package com.myk.game.gobangchess.rooms;

import com.myk.game.gobangchess.CustomBottom;
import com.myk.game.gobangchess.DataManager;
import com.myk.game.gobangchess.Player;
import com.myk.game.gobangchess.Sign;
import com.myk.game.gobangchess.constants.WinMode;
import com.myk.game.gobangchess.rooms.absrooms.AbsChessRoom;
import com.myk.game.gobangchess.signs.*;
import com.myk.game.gobangchess.utils.GridsUtils;
import com.myk.game.gobangchess.utils.Pair;

import java.util.*;

/**
 * 生肖象棋
 * 图文规则：
 * https://tieba.baidu.com/p/4964421757
 * https://tieba.baidu.com/p/4971483997
 * 看视频更好理解：
 * https://v.youku.com/v_show/id_XMzUyOTk3NDE0NA==.html?spm=a2hje.13141534.1_3.d_18&scm=20140719.manual.240103.video_XMzUyOTk3NDE0NA==
 * https://v.youku.com/v_show/id_XMzU0MzEwNTYwMA==.html?spm=a2hzp.8244740.0.0
 * https://v.youku.com/v_show/id_XMzgwMzgzNjM0OA==.html?spm=a2hje.13141534.1_3.d_16&scm=20140719.manual.240103.video_XMzgwMzgzNjM0OA==
 */
public class ZodiacChessRoom extends AbsChessRoom {

    //++ 判断是否无棋可走需要优化，单独增加一个抽象方法，默认根据可以移动格子数是否为0来判断，但是有的棋子还能有特效，就需要重写
    //++ 然后记得加上战报，方便复盘
    //++ 最后要记得自己整理一份规则出来

    //++ 最后自己试玩，遇到bug改bug，顺便优化体验

    private CacheSign cacheSign; //缓存标记

    public static final int midLineRow = 4; //中心线在第5行和第6行之间
    public static final int blackSideLingRow = 2; //黑方阵营线在第3行和第4行之间
    public static final int whiteSideLingRow = 6; //白方阵营线在第7行和第8行之间

    public List<Chess> chessList; //场上棋子集合
    public Chess choose; //当前选中的棋子
    public Set<Chess> waitEatChesses; //当前被包围的等待被吃的棋子们
    public List<Chess> waitChooseChesses; //当前指定可选的棋子们（主要是鸡用）
    public List<Chess> canSeduceChesses; //当前指定可吸的棋子们（主要时兔用）
    public boolean interruptEndCheckFlag; //是否中断结束检查，正常执行完移动操作后就要自动执行结束检查，有些棋子的特效需要玩家进行选择，那就不要自动执行结束检查了，改为玩家点击事件后再执行
    public List<int[]> moveArrowList;//移动箭头状态
    public List<int[]> putArrowList;//被移动箭头状态
    public IPlayerClickGridEventListener playerClickGridEventListener; //玩家点击棋盘事件监听
    public RoundInteractiveActionsChain roundInteractiveActionsChain; //一回合玩家全部交互行动的链

    public ZodiacChessRoom(String roomName) {
        super(roomName, "生肖象棋又称生肖棋，以十二生肖为棋子的双人棋盘游戏。;;融合了象棋类的移动方式，围棋类的吃子方式，此外还有丰富的技能效果，使棋局变化丰富。", "生肖象棋", GridsUtils.createEmptyGrids(10, 6), true, false);
        initChessBoardStyle();
    }

    /**
     * 初始化棋盘样式
     */
    public void initChessBoardStyle() {
        List<Sign> chessboardStyle = new ArrayList<>();
        int maxRow = curGrids.length - 1;
        int maxCol = curGrids[0].length - 1;

        //画灰色格子线
        for (int i = 1; i <= maxRow; i++) {
            List<Pair<Integer, Integer>> nodes1 = new ArrayList<>();
            nodes1.add(new Pair<>(getIndex(i, 0), 0)); //起点
            nodes1.add(new Pair<>(getIndex(i, maxCol), 2)); //终点
            chessboardStyle.add(new PathSign(nodes1, "#FF666666", "#00000000", 1f, 0));
        }
        for (int j = 1; j <= maxCol; j++) {
            List<Pair<Integer, Integer>> nodes1 = new ArrayList<>();
            nodes1.add(new Pair<>(getIndex(0, j), 0)); //起点
            nodes1.add(new Pair<>(getIndex(maxRow, j), 6)); //终点
            chessboardStyle.add(new PathSign(nodes1, "#FF666666", "#00000000", 1f, 0));
        }

        //画黑色外边框
        chessboardStyle.add(new GroundSign(getIndex(0, 0), getIndex(maxRow, maxCol), "#00000000", "#FF000000", 1.8f, 0, 0));

        //画黑线中心线
        List<Pair<Integer, Integer>> centerLine = new ArrayList<>();
        centerLine.add(new Pair<>(getIndex(midLineRow, 0), 6));
        centerLine.add(new Pair<>(getIndex(midLineRow, maxCol), 8));
        chessboardStyle.add(new PathSign(centerLine, "#FF000000", "#FFFFFFFF", 1.8f, 0));

        //画黑方阵营线 （黑方在上，黑方棋子是红色的，阵营线为蓝色）
        List<Pair<Integer, Integer>> blackSideLine = new ArrayList<>();
        blackSideLine.add(new Pair<>(getIndex(blackSideLingRow, 0), 6));
        blackSideLine.add(new Pair<>(getIndex(blackSideLingRow, maxCol), 8));
        chessboardStyle.add(new PathSign(blackSideLine, "#FF0000FF", "#FFFFFFFF", 1.8f, 0));

        //画白方阵营线 （白方在上，白方棋子是蓝色的，阵营线为红色）
        List<Pair<Integer, Integer>> whiteSideLine = new ArrayList<>();
        whiteSideLine.add(new Pair<>(getIndex(whiteSideLingRow, 0), 6));
        whiteSideLine.add(new Pair<>(getIndex(whiteSideLingRow, maxCol), 8));
        chessboardStyle.add(new PathSign(whiteSideLine, "#FFFF0000", "#FFFFFFFF", 1.8f, 0));

        //将背景封装为缓存标记
        cacheSign = new CacheSign(this, "background", 5, true, chessboardStyle);  //*** 修改背景样式记得改这里的版本号！！！
    }

    @Override
    public CacheSign getCacheSignByName(String cacheName) {
        return cacheSign;
    }

    @Override
    public String getRuleText() {
        return "《生肖象棋》\n" +
                "{{一、 判定胜负方法@back=#228899}}\n" +
                "鼠在属相中排序第一，游戏中可以决定胜负。\n" +
                "第一:自己的老鼠抵达对方阵营前一排，开局在理想状态下己方老鼠向前连续走6步即可，己方获胜。\n" +
                "第二:己方棋子主动占领对方老鼠上下左右其中任意一个方格，己方获胜。（如果对方老鼠主动走到己方棋子旁边不能算己方赢。）\n" +
                "第三:对方棋子连同老鼠被己方包围提子，己方获胜。\n" +
                "第四:对方无子可下，己方获胜。\n" +
                "{{二、 基本概念@back=#228899}}\n" +
                "提子：同围棋一样方法提对方棋子，不同点是没有禁着点，就是吃子后，对方其他子仍然可以进入此点。移动后产生提子情况时必须提。\n" +
                "特技：一部分棋子拥有使其他棋子移动的能力。包括牛、兔、龙、狗、猪。 可以使对方或己方子移动。\n" +
                "牛特技顶：和一个棋子一起向对方移动一格。\n" +
                "兔特技吸：移动后使横向纵向棋子移动一格紧贴自己。\n" +
                "龙特技交换：和横向纵向相邻的一个棋子交换位置。\n" +
                "狗特技咬：和一个棋子一起向自己移动一格。\n" +
                "猪特技横移：带动左右的一个棋子横向移动。\n" +
                "　　联手:己方或者对方的两个棋子上下或左右相互靠着称作联手。联手的棋子可阻止被对方移动，但是对方棋子被激励后，联手无效。联手对兔子无效，是指兔子不需要受到激励就可以破坏对方联手。己方移动自己不受联手限制。\n" +
                "　　中心线（国界）:开局在理想状态下自己的老鼠向前连续走3步即可抵达。\n" +
                "　　激励:如鼠抵达中心线或再向前，对己方棋子都产生激励效果。老鼠退离中心线后则失去激励效果。鸡可以使上下左右己方的棋子受到激励。\n" +
                "　　强化特技:受到激励，有特技的棋子可以有加强能力，破坏对方联手。\n" +
                "　　强化走法:受到激励，有的棋子可以有强化行走能力。\n" +
                "　　连锁:己方的鸡移动到己方一个棋子旁边，能够带动这个棋子再行动一次（条件是这个棋子本回合没有动过），有时候会产生多次的连锁移动。兔子有时也会产生小的连锁。\n" +
                "　　变招：重复连将对方老鼠3次不赢要率先变招。" +
                "{{三、 基本走法：@back=#228899}}\n" +
                "鼠:前后左右走只能走一格，抵达中心线后可以斜行过中心线，过中心线后斜行直行均可，但每步只能走一格，后退离开中心线（国界）恢复原来走法。抵达中心线后对自己棋子有激励作用。\n" +
                "　　牛:前后左右走只能走一格。有顶的特技。\n" +
                "　　虎:前后左右走一格或者二格，有越过一个子的强化走法。\n" +
                "　　兔:斜向只能走一格。有吸的特技。\n" +
                "　　龙:横向，竖向，斜向只能走一格。有交换的特技。\n" +
                "　　蛇:只能斜行。格数不限，不能越子。\n" +
                "　　马:没有中国象棋中蹩马腿的限制。走法相当于国际象棋的马。\n" +
                "　　羊:前后左右走只能走一格。可以斜着跳过临近的一个棋子。比如开局羊可以斜着从龙跳到虎前面。可以连跳。有变向的强化走法。强化走法时可跳过相连的一串棋子。\n" +
                "　　猴:前后左右走只能走一格。可以横向，竖向跳过临近的一个棋子。可以连跳。有变向的强化走法。\n" +
                "　　鸡:前后左右走只能走一格。对上下左右己方的四个子有激励或连锁作用。\n" +
                "　　狗:前后左右只能走一格，靠近对方棋子时可以靠着对方棋子斜行走一格。有咬的特技。\n" +
                "　　猪:前后只能走一格，左右走不限制步数，不能越子。有横移的特技。\n" +
                "{{四、 特技用法和一些补充说明@back=#228899}}\n" +
                "　　1牛的顶，分为两种，普通的顶和冲顶。冲顶就是两个棋子之间有一个空格。牛只能顶动一个子，那个子后面一定是空格。\n" +
                "　　2 龙的交换，如果和对方龙交换，则对方龙不能马上再交换过来。其他不受影响。龙不可以斜向交换。如对方鼠靠近己方龙，而且己方龙能和鼠交换位置，己方马上赢。\n" +
                "　　3 兔只有移动（不包括被对方移动）后才能吸。最多吸四个棋子，可以选择吸或不吸，可能会产生一个小小的连锁。被吸引的对方棋子下回合自己不能移动，只有特技可移动它，兔吸己方棋子则不受任何限制。兔只能吸本轮没有动过的子。\n" +
                "　　4 狗的咬，被咬对方的棋子下回合不能移动，特技也不能移动它。狗咬己方棋子则不受任何限制。狗不能连续两个回合使用咬的特技。\n" +
                "　　5 猪的横移，如要横移（或者横移结束）的这两个子上下左右均无其他棋子，那么这两个棋子可以交换位置。可以先横移后交换，或者先交换后横移。横移格数不限。\n" +
                "　　6 鸡只有移动（不包括被对方移动）后才能发挥连锁作用。\n" +
                "　　7 羊没有特技，在强化走法时，可以跳过相连的一串棋子。" +
                "棋理简述\n" +
                "1.赢棋主要方法是让自己棋子主动走到对方鼠旁边，或移动对方鼠。\n" +
                "2.通过棋子特技的推（牛），拉（狗、兔），交换（龙），横移（猪）来改变彼此阵型，穿插马羊猴等棋子跳跃和移动，达到赢棋目的。\n" +
                "3.战术上可以用围棋的包围(提子后无禁着点)的方法消灭对方棋子。\n" +
                "4.阻止被对方棋子移动基本方法就是联手，即两个己方棋子相连。但是联手遇对方兔无效。\n" +
                "5.激励的棋子也可以让联手无效，鼠，鸡，在一定条件下可以激励己方棋子。\n" +
                "6.鼠抵达对方阵营前一排，或提子里有对方鼠，或对方无棋可走均可赢棋。\n" +
                "行棋走法\n" +
                "鼠：直走一格。走到中心线前移动加强（可斜上），并能激励全体，过中心线后直行或斜行，退回中心线恢复原来走法。注意重复将军时不能超过三次。\n" +
                "牛：直走一格。【特技顶】在同轴方向牛前移推顶或冲顶棋子。牛被激励后对方联手无效。\n" +
                "虎：直走一格或两格。虎被激励后可跳过旁边的一棋子。\n" +
                "兔：斜走一格。【特技吸】兔移动后使横向纵向棋子（此棋子本回合没动过）移动一格紧贴着兔。最多吸四个可选吸或不吸，对方被吸棋子下回合自己不能动。对方联手对己方兔无效。\n" +
                "龙：直或斜走一格。【特技交换】龙和横向纵向相邻一个棋子交换位置，龙与龙交换后不能马上换回。龙被激励后对方联手无效。\n" +
                "蛇：斜行格数不限。不可越子。\n" +
                "马：走三乘二对角方格。可越子。\n" +
                "羊：直走一格。可斜向跳过斜方临近的一个棋子，可连跳但不能变向。被激励后可变向连跳、跨越多个棋子。\n" +
                "猴：直走一格。可直线跳过旁边一个棋子，可连跳但不能变向。被激励后可以变向连跳。\n" +
                "鸡：直走一格。移动后可使旁边的一个己方棋子（此棋子本回合没动过）再行动一次。鸡对四邻未动己方棋子有激励作用。\n" +
                "狗：直走一格。可靠着对方棋子斜行一格。【特技咬】在同轴方向狗后退拖咬旁边棋子移动一格，被拖咬的对方棋子下回合自己不能动或被移动。狗不能连续拖咬。狗被激励后对方联手无效。\n" +
                "猪：纵向走一格。横向不限不可越子。【特技横移】猪带动旁边左或右其中一个棋子横移。这两个棋子旁边前后左右无棋子时可交换位置，交换后可连贯带动棋子横移，或次序相反。猪被激励后对方联手无效。";
    }

    @Override
    public void doRule(int row, int col, int value) {
        if (playerClickGridEventListener != null) {
            if (value == 1) {
                playerClickGridEventListener.onPlayerClickGrid(this, player_black, row, col, value);
            }
            else if (value == 2) {
                playerClickGridEventListener.onPlayerClickGrid(this, player_white, row, col, value);
            }
        }
    }

    @Override
    protected void onStartPrepare() {
        initChessList(); //重新创建初始布局的棋子们
        //initTestChessList(); //重新创建初始布局的棋子们
        updateAllChessesState(); //更新所有棋子状态
        noticeBlackMove(); //先通知玩家行动修改了cur
        setWaitPlayerChooseChess(); //再设置棋子点击监听器，等待选择
    }

    @Override
    public void resetGrids() {
        super.resetGrids();
        chessList = null;
        choose = null;
        waitEatChesses = null;
        waitChooseChesses = null;
        canSeduceChesses = null;
        interruptEndCheckFlag = false;
        moveArrowList = null;
        putArrowList = null;
        playerClickGridEventListener = null;
        roundInteractiveActionsChain = null;
    }

    @Override
    public String getGameInfo() {

        //先全部初始化
        curGrids = GridsUtils.copyGrids(originGrids);
        int[][] canvasGrids = GridsUtils.copyGrids(curGrids);
        signList.clear();
        signList.add(cacheSign);

        //根据chessList集合和choose对象来生成要显示到客户端的布局和标记
        if (chessList != null && chessList.size() > 0) {
            //绘制棋子
            for (Chess chess : chessList) {
                curGrids[chess.row][chess.col] = chess.side; //缓存棋子的底座
                canvasGrids[chess.row][chess.col] = chess.side + 10; //显示棋子的底座
                signList.add(new TextSign(getIndex(chess.row, chess.col), chess.image, chess.getColor())); //显示棋子的文字
                if (chess.inspireState.isOpen) { //激励状态的棋子左下角显示红色角标
                    signList.add(new BadgeSign(getIndex(chess.row, chess.col), "激", 6, "#FFFFFF00", "#99FF0000"));
                }
                if (chess.bittenState.isOpen) { //被咬住状态右下角显示 角标
                    signList.add(new BadgeSign(getIndex(chess.row, chess.col), "咬", 8, "#FFFFFF00", "#99802A2A"));
                }
                if (chess.seduceState.isOpen) { //被吸引状态右上角显示粉色角标
                    signList.add(new BadgeSign(getIndex(chess.row, chess.col), "吸", 8, "#FFFFFF00", "#99FFC0CB"));
                }
                if (chess.deathState.isOpen) { //死亡状态的棋子红红色X标记
                    signList.add(new TextSign(getIndex(chess.row, chess.col), "X", "#FFFF0000"));
                }
                if (chess instanceof Pig && ((Pig) chess).carryState.carryingChess != null) {
                    List<Integer> integers = new ArrayList<>();
                    integers.add(getIndex(chess.row, chess.col));
                    integers.add(getIndex(((Pig) chess).carryState.carryingChess.row, ((Pig) chess).carryState.carryingChess.col));
                    signList.add(new AroundSign(integers, "#00FF0000", "#EEEEEE66", 2f));
                }
            }

            //绘制对弈时的选中棋子移动状态
            if (choose != null) {
                //绘制棋子可移动路径
                List<int[]> canMovedPosList = choose.getCanMovedPos(this); //获取该棋子可移动到的格子的坐标集合
                if (canMovedPosList.size() > 0) {
                    List<Integer> ids = new ArrayList<>();
                    for (int[] canMovedPos : canMovedPosList) {
                        ids.add(getIndex(canMovedPos[0], canMovedPos[1]));
                    }
                    signList.add(new ColorSign(ids, "#9988EE88")); //可移动到的格子用更淡的绿色表示
                }
                //绘制棋子选中状态
                signList.add(new GroundSign(getIndex(choose.row, choose.col), "#00FFFFFF", "#FF33CC33", 2F, 0, 1));
            }

            //绘制主动移动箭头
            if (moveArrowList != null && moveArrowList.size() > 0) {
                for (int[] moveArrow : moveArrowList) {
                    signList.add(new LineSign(moveArrow[0], moveArrow[1], "#EE66EE66", 1));
                }
            }
            //绘制被动移动箭头
            if (putArrowList != null && putArrowList.size() > 0) {
                for (int[] putArrow : putArrowList) {
                    signList.add(new LineSign(putArrow[0], putArrow[1], "#EEEEEE66", 1));
                }
            }
        }

        return "" + step +
                "&" + cur +
                "&" + blackScore +
                "&" + whiteScore +
                "&" + gameTip +
                "&" + GridsUtils.grids2Str(canvasGrids) +
                "&" + getSignsInfo() +
                "&" + getCoordMode();
    }

    /**
     * 获取当前行动方文字
     */
    private String getCurText() {
        if (cur == 1) return "请黑方";
        else if (cur == 2) return "请白方";
        return "无人能";
    }

    /**
     * 添加主动移动的提示箭头（会自动移除上个回合的，也会自动移除返回的路径）
     */
    private void addMoveArrow(int oldRow, int oldCol, int newRow, int newCol) {
        int[] moveArrow = new int[]{getIndex(oldRow, oldCol), getIndex(newRow, newCol), this.step}; //创建移动箭头提示
        if (moveArrowList == null || moveArrowList.size() == 0 || moveArrowList.get(0)[2] != this.step) {
            moveArrowList = new ArrayList<>();
            moveArrowList.add(moveArrow);
        }
        else {
            int[] lastMoveArrow = moveArrowList.get(moveArrowList.size() - 1);
            if (lastMoveArrow[0] == moveArrow[1] && lastMoveArrow[1] == moveArrow[0]) {
                moveArrowList.remove(moveArrowList.size() - 1);
            }
            else {
                moveArrowList.add(moveArrow);
            }
        }

        //由于主动移动每回合都有被动移动不是每回合都有，所以在这里自动移除上回合的被动移动的提示箭头
        if (putArrowList != null && putArrowList.size() > 0 && putArrowList.get(0)[2] != this.step) {
            putArrowList = null;
        }
    }

    /**
     * 添加被动移动的提示箭头（会自动移除上个回合的，也会自动移除返回的路径）
     */
    private void addPutArrow(int oldRow, int oldCol, int newRow, int newCol) {
        int[] putArrow = new int[]{getIndex(oldRow, oldCol), getIndex(newRow, newCol), this.step}; //创建移动箭头提示
        if (putArrowList == null || putArrowList.size() == 0 || putArrowList.get(0)[2] != this.step) {
            putArrowList = new ArrayList<>();
            putArrowList.add(putArrow);
        }
        else {
            int[] lastMoveArrow = putArrowList.get(putArrowList.size() - 1);
            if (lastMoveArrow[0] == putArrow[1] && lastMoveArrow[1] == putArrow[0]) {
                putArrowList.remove(putArrowList.size() - 1);
            }
            else {
                putArrowList.add(putArrow);
            }
        }
    }

    /**
     * 根据坐标搜索棋子
     */
    public Chess getChessByPos(int row, int col) {
        for (Chess chess : chessList) {
            if (chess.row == row && chess.col == col) {
                return chess;
            }
        }
        return null;
    }

    /**
     * 获取指定方的棋子鼠
     */
    public Mouse getMouseChess(int side) {
        for (Chess chess : chessList) {
            if (chess.side == side && chess instanceof Mouse) {
                return (Mouse) chess;
            }
        }
        return null;
    }

    /**
     * 判断两个位置之间是否相望（直线中间路径没有其它棋子）
     * 参数的两个位置确保不相同
     */
    public boolean isPosStraightMeet(int fromRow, int fromCol, int toRow, int toCol) {
        int rowDiff = Math.abs(fromRow - toRow);
        int colDiff = Math.abs(fromCol - toCol);
        int maxDiff = Math.max(rowDiff, colDiff);
        if (rowDiff != 0 && colDiff != 0) return false; //非直线方向不能被交换
        int rAdd = (int) Math.signum(toRow - fromRow);
        int cAdd = (int) Math.signum(toCol - fromCol);
        for (int i = 1; i < maxDiff; i++) {
            int r = fromRow + rAdd * i;
            int c = fromCol + cAdd * i;
            Chess stop = getChessByPos(r, c);
            if (stop != null) return false;
        }
        return true;
    }

    /**
     * 判断两个位置是否在45°对角线上相望（路径中间没有其它棋子）
     * 参数的两个位置确保不相同
     */
    public boolean isPosDiagonalMeet(int fromRow, int fromCol, int toRow, int toCol) {
        int rowDiff = Math.abs(fromRow - toRow);
        int colDiff = Math.abs(fromCol - toCol);
        int maxDiff = Math.max(rowDiff, colDiff);
        if (rowDiff != colDiff) return false; //非对角线方向不能被交换
        int rAdd = (int) Math.signum(toRow - fromRow);
        int cAdd = (int) Math.signum(toCol - fromCol);
        for (int i = 1; i < maxDiff; i++) {
            int r = fromRow + rAdd * i;
            int c = fromCol + cAdd * i;
            Chess stop = getChessByPos(r, c);
            if (stop != null) return false;
        }
        return true;
    }

    /**
     * 判断两个位置是否在45°对角线上连接（中间充满其它棋子）
     */
    public boolean isPosDiagonalConnect(int fromRow, int fromCol, int toRow, int toCol) {
        int rowDiff = Math.abs(fromRow - toRow);
        int colDiff = Math.abs(fromCol - toCol);
        int maxDiff = Math.max(rowDiff, colDiff);
        if (rowDiff != colDiff) return false; //非对角线方向不能被交换
        int rAdd = (int) Math.signum(toRow - fromRow);
        int cAdd = (int) Math.signum(toCol - fromCol);
        for (int i = 1; i < maxDiff; i++) {
            int r = fromRow + rAdd * i;
            int c = fromCol + cAdd * i;
            Chess stop = getChessByPos(r, c);
            if (stop == null) return false;
        }
        return true;
    }

    /**
     * 初始化棋子布局，固定阵型
     */
    private void initChessList() {
        chessList = new ArrayList<>();
        chessList.add(new Mouse(1, 1, 0));
        chessList.add(new Buffalo(1, 0, 0));
        chessList.add(new Tiger(1, 1, 1));
        chessList.add(new Rabbit(1, 0, 1));
        chessList.add(new Loong(1, 1, 2));
        chessList.add(new Snake(1, 0, 2));
        chessList.add(new Horse(1, 1, 3));
        chessList.add(new Sheep(1, 0, 3));
        chessList.add(new Monkey(1, 1, 4));
        chessList.add(new Chicken(1, 0, 4));
        chessList.add(new Dog(1, 1, 5));
        chessList.add(new Pig(1, 0, 5));

        chessList.add(new Mouse(2, 8, 5));
        chessList.add(new Buffalo(2, 9, 5));
        chessList.add(new Tiger(2, 8, 4));
        chessList.add(new Rabbit(2, 9, 4));
        chessList.add(new Loong(2, 8, 3));
        chessList.add(new Snake(2, 9, 3));
        chessList.add(new Horse(2, 8, 2));
        chessList.add(new Sheep(2, 9, 2));
        chessList.add(new Monkey(2, 8, 1));
        chessList.add(new Chicken(2, 9, 1));
        chessList.add(new Dog(2, 8, 0));
        chessList.add(new Pig(2, 9, 0));
    }

    //初始测试布局
    private void initTestChessList() {
        chessList = new ArrayList<>();
        chessList.add(new Mouse(1, 1, 0));
        chessList.add(new Rabbit(1, 0, 4));
        chessList.add(new Pig(1, 0, 5));
        chessList.add(new Tiger(1, 2, 3));

        chessList.add(new Mouse(2, 8, 5));
        chessList.add(new Buffalo(2, 9, 5));
        chessList.add(new Tiger(2, 8, 4));
        chessList.add(new Rabbit(2, 9, 4));
        chessList.add(new Loong(2, 8, 3));
        chessList.add(new Snake(2, 9, 3));
        chessList.add(new Horse(2, 8, 2));
        chessList.add(new Sheep(2, 9, 2));
        chessList.add(new Monkey(2, 8, 1));
        chessList.add(new Chicken(2, 9, 1));
        chessList.add(new Dog(2, 8, 0));
        chessList.add(new Pig(2, 9, 0));
    }

    /**
     * 设置点击监听，用户等待选择
     */
    private void setWaitPlayerChooseChess() {
        roundInteractiveActionsChain = new RoundInteractiveActionsChain(this, getCurPlayer());
        if (waitChooseChesses != null) {
            StringBuilder ns = new StringBuilder();
            for (int i = 0; i < waitChooseChesses.size(); i++) {
                ns.append(waitChooseChesses.get(i).name);
                if (i < waitChooseChesses.size() - 1) ns.append(",");
            }
            roundInteractiveActionsChain.setActionGameTip("选择[" + ns + "]连锁移动");
        }
        else {
            roundInteractiveActionsChain.setActionGameTip("选择棋子行动");
        }
        roundInteractiveActionsChain.setPlayerClickGridEventListener((room, player, row, col, side) -> {
            Chess chess = getChessByPos(row, col);
            if (chess != null) { //选中棋子
                if (chess.deathState.isOpen) { //选中了已经死亡过的棋子
                    setWaitPlayerChooseChess(); //还原到未选中状态
                    noticeAllRefreshGameInfo(); //仅仅刷新显示，还能继续点击
                }
                else if (chess.side != side) { //选中对方棋子
                    setWaitPlayerChooseChess(); //还原到未选中状态
                    noticeAllRefreshGameInfo(); //仅仅刷新显示，还能继续点击
                }
                else if (waitChooseChesses != null && !waitChooseChesses.contains(chess)) { //选中了非指定待选棋子时
                    setWaitPlayerChooseChess(); //还原到未选中状态
                    noticeAllRefreshGameInfo(); //仅仅刷新显示，还能继续点击
                }
                else { //选中己方棋子
                    doChooseChessAction(chess); //执行选中棋子动作
                    noticeAllRefreshGameInfo(); //仅仅刷新显示，还能继续点击
                }
            }
            else { //选中空格
                setWaitPlayerChooseChess();//还原到未选中状态
                noticeAllRefreshGameInfo(); //仅仅刷新显示，还能继续点击
            }
        });
        //分发交互设置
        roundInteractiveActionsChain.dispatchInteractiveSetting();
    }

    /**
     * 设置点击监听，等待用户提走对方被包围棋子（标记为死亡的棋子）
     */
    private void setClickListenerWaitEatChesses() {
        gameTip = getCurText() + "提走被包围棋子";
        playerClickGridEventListener = (room, player, row, col, side) -> {
            Chess chess = getChessByPos(row, col);
            if (chess != null && waitEatChesses.contains(chess)) {
                chessList.removeAll(waitEatChesses); //被包围的棋子从棋子列表中删除
                waitEatChesses = null; //制空waitEatChesses
                updateAllChessesState(); //有棋子被移除了，重新更新所有棋子状态
                checkGameOver();//再接着执行被中断的回合结束判断
            }
        };
    }

    /**
     * 设置动态下发得按钮
     */
    private void setCustomButtonClickListener(int side, String title, Runnable callable) {
        if (side == 1 && player_black != null) {
            player_black.registerCustomBottom(new CustomBottom(this, title, player -> {
                callable.run();
                player_black.removeCustomBottom(player_black.findCustomBottomByTitle(this, title));
                player_black.dispatchCustomBottom();
            }));
        }
        else if (side == 2 && player_white != null) {
            player_white.registerCustomBottom(new CustomBottom(this, title, player -> {
                callable.run();
                player_white.removeCustomBottom(player_white.findCustomBottomByTitle(this, title));
                player_white.dispatchCustomBottom();
            }));
        }
    }

    /**
     * 移除动态下发的按钮
     */
    private void removeCustomButtonClickListener(int side, String title) {
        if (side == 1 && player_black != null) {
            player_black.removeCustomBottom(player_black.findCustomBottomByTitle(this, title));
            player_black.dispatchCustomBottom();
        }
        else if (side == 2 && player_white != null) {
            player_white.removeCustomBottom(player_white.findCustomBottomByTitle(this, title));
            player_white.dispatchCustomBottom();
        }
    }

    /**
     * 取消选中棋子
     */
    private void cancelChoose() {
        if (choose != null) choose.onCancelChosen(this);
        choose = null;
    }

    /**
     * 执行选中棋子操作
     */
    private void doChooseChessAction(Chess chess) {
        interruptEndCheckFlag = false; //该方法肯定是新的主动行为，把中断结束标志取消，以免因为 鸡 的特效导致永远不能进行结束判断。
        choose = chess;
        choose.onChosen(this); //被选中的棋子回调被选中事件
    }

    /**
     * 执行棋子移动操作
     */
    private void doMoveChessAction(Chess chess, int row, int col) {
        int oldRow = chess.row;
        int oldCol = chess.col;
        choose = null; //移动过后清除选中状态
        addMoveArrow(oldRow, oldCol, row, col); //添加移动箭头提示
        chess.onMoved(this, row, col); //执行棋子的移动回调
        roundInteractiveActionsChain.checkRoundOver();
    }

    /**
     * 主动行动结束与否判断
     */
    private void onDoActionOver() {
        if (hasSurroundedChessesWaitEat()) { //有棋子被包围时
            setClickListenerWaitEatChesses(); //设置等待提走被包围棋子
            noticeAllRefreshGameInfo(); //仅仅刷新显示，还能继续点击
        }
        else if (interruptEndCheckFlag) { //有棋子特效需要玩家确认，就不自动执行移动行为结束操作了
            noticeAllRefreshGameInfo(); //仅仅刷新显示，还能继续点击
        }
        else { //自动执行移动行为结束操作
            updateAllChessesState(); //没有棋子等待被吃时，记得执行更新状态
            checkGameOver(); //执行移动行为结束操作
        }
    }

    /**
     * 执行棋子被特效移动操作
     */
    private void doPutChessAction(Chess chess, int row, int col) {
        int oldRow = chess.row;
        int oldCol = chess.col;
        addPutArrow(oldRow, oldCol, row, col); //添加被移动箭头提示
        chess.onPut(this, row, col); //执行棋子的被特效移动回调
    }

    /**
     * 是否还拥有可以移动的棋子
     */
    public boolean hasCanMovedChess(int side) {
        for (Chess chess : chessList) {
            if (chess.side == side && chess.getCanMovedPos(this).size() > 0) {
                return true;
            }
        }
        return false;
    }

    /**
     * 当前存在等待被吃的被包围棋子
     */
    private boolean hasSurroundedChessesWaitEat() {
        return waitEatChesses != null && waitEatChesses.size() > 0;
    }

    /**
     * 检查是否走到了对方鼠的旁边
     */
    private void checkMoveToOthMouse(Chess chess) {
        Chess chessA = getChessByPos(chess.row - 1, chess.col);
        if (chessA != null && chessA instanceof Mouse && chessA.side != chess.side) {
            chessA.deathState.open(this);
        }
        Chess chessB = getChessByPos(chess.row + 1, chess.col);
        if (chessB != null && chessB instanceof Mouse && chessB.side != chess.side) {
            chessB.deathState.open(this);
        }
        Chess chessC = getChessByPos(chess.row, chess.col - 1);
        if (chessC != null && chessC instanceof Mouse && chessC.side != chess.side) {
            chessC.deathState.open(this);
        }
        Chess chessD = getChessByPos(chess.row, chess.col + 1);
        if (chessD != null && chessD instanceof Mouse && chessD.side != chess.side) {
            chessD.deathState.open(this);
        }
    }

    /**
     * 判断某一方棋子是否被sourse(及它的队友)包围，被包围的棋子全部标记为死亡
     */
    public void checkSurroundedChesses(Chess sourse) {
        Set<Chess> surroundedSet = new HashSet<>();
        for (Chess chess : chessList) {
            if (chess.side != sourse.side) {
                Set<Chess> visitedSet = new HashSet<>();
                if (isSurrounded(chess, visitedSet)) {
                    if (checkSurroundedByChess(visitedSet, sourse)) {
                        chess.deathState.open(this);
                        surroundedSet.add(chess);
                    }
                }
            }
        }
        if (surroundedSet.size() > 0) {
            if (this.waitEatChesses != null) waitEatChesses.addAll(surroundedSet);
            else this.waitEatChesses = new HashSet<>(surroundedSet);
        }
    }

    /**
     * 判断包围是否因为sourse导致的
     * （因为包围圈中有空格不算包围，所以肯定会出现的情况是：visitedSet中某个棋子和sourse相连）
     */
    private boolean checkSurroundedByChess(Set<Chess> visitedSet, Chess sourse) {
        for (Chess chess : visitedSet) {
            if (getChessByPos(chess.row + 1, chess.col) == sourse) return true;
            if (getChessByPos(chess.row - 1, chess.col) == sourse) return true;
            if (getChessByPos(chess.row, chess.col + 1) == sourse) return true;
            if (getChessByPos(chess.row, chess.col - 1) == sourse) return true;
        }
        return false;
    }

    /**
     * 判断某个棋子是否被对方棋子（和边界线）包围了
     */
    private boolean isSurrounded(Chess chess, Set<Chess> visited) {
        visited.add(chess);
        //一旦查到旁边有空格就返回false
        if (chess.row > 0 && getChessByPos(chess.row - 1, chess.col) == null) return false;
        if (chess.row < curGrids.length - 1 && getChessByPos(chess.row + 1, chess.col) == null) return false;
        if (chess.col > 0 && getChessByPos(chess.row, chess.col - 1) == null) return false;
        if (chess.col < curGrids[0].length - 1 && getChessByPos(chess.row, chess.col + 1) == null) return false;
        //搜索四周友军是否有出路
        if (chess.row > 0) {
            Chess chessA = getChessByPos(chess.row - 1, chess.col);
            if (chessA != null && chessA.side == chess.side && !visited.contains(chessA)) {
                if (!isSurrounded(chessA, visited)) return false;
            }
        }
        if (chess.row < curGrids.length - 1) {
            Chess chessA = getChessByPos(chess.row + 1, chess.col);
            if (chessA != null && chessA.side == chess.side && !visited.contains(chessA)) {
                if (!isSurrounded(chessA, visited)) return false;
            }
        }
        if (chess.col > 0) {
            Chess chessA = getChessByPos(chess.row, chess.col - 1);
            if (chessA != null && chessA.side == chess.side && !visited.contains(chessA)) {
                if (!isSurrounded(chessA, visited)) return false;
            }
        }
        if (chess.col < curGrids[0].length - 1) {
            Chess chessA = getChessByPos(chess.row, chess.col + 1);
            if (chessA != null && chessA.side == chess.side && !visited.contains(chessA)) {
                if (!isSurrounded(chessA, visited)) return false;
            }
        }
        return true; //所有可能的出口都没有，那就一定是被包围死了
    }

    /**
     * 更新所有棋子的状态
     */
    private void updateAllChessesState() {
        for (Chess chess : chessList) {
            chess.updateState(this);
        }
    }

    /**
     * 检查游戏是否结束
     */
    private void checkGameOver() {
        stepAdd(); //增加回合，但是这里可能会与连锁冲突
        Mouse blackMouse = getMouseChess(1);
        Mouse whiteMouse = getMouseChess(2);
        if (cur == 1) {
            if (blackMouse.row > whiteSideLingRow) {
                gameTip = "黑方[鼠]抵达白方阵营前获胜！";
                doOver(WinMode.BLACK_WIN, "黑方[鼠]抵达白方阵营前获胜！");
            }
            else if (whiteMouse == null || whiteMouse.deathState.isOpen) {
                gameTip = "黑方成功捕获白方[鼠]获胜！";
                doOver(WinMode.BLACK_WIN, "黑方成功捕获白方[鼠]获胜！");
            }
            else if (!hasCanMovedChess(2)) {
                gameTip = "白方已无可走，黑方获胜！";
                doOver(WinMode.BLACK_WIN, "白方已无可走，黑方获胜！");
            }
            else {
                cur = 2;
                setWaitPlayerChooseChess();
                noticeWhiteMove();
            }
        }
        else if (cur == 2) {
            if (whiteMouse.row <= blackSideLingRow) {
                gameTip = "白方[鼠]抵达黑方阵营前获胜！";
                doOver(WinMode.WHITE_WIN, "白方[鼠]抵达黑方阵营前获胜！");
            }
            else if (blackMouse == null || blackMouse.deathState.isOpen) {
                gameTip = "白方成功捕获黑方[鼠]获胜！";
                doOver(WinMode.WHITE_WIN, "白方成功捕获黑方[鼠]获胜！");
            }
            else if (!hasCanMovedChess(1)) {
                gameTip = "黑方已无可走，白方获胜！";
                doOver(WinMode.WHITE_WIN, "黑方已无可走，白方获胜！");
            }
            else {
                cur = 1;//这里要提前更新cur因为setClickListenerWaitChoose()中会根据cur来更新gameTip的显示
                setWaitPlayerChooseChess();
                noticeBlackMove();
            }
        }
    }

    /**
     * 通用移动限制，只能直线走1格
     */
    public final boolean generalMoveLimitStraightOneStep(Chess chess, int row, int col) {
        if (Math.abs(row - chess.row) + Math.abs(col - chess.col) != 1) return false;
        return generalMoveLimit(chess, row, col);
    }

    /**
     * 通用移动限制，只能斜线走1格
     */
    public final boolean generalMoveLimitDiagonalOneStep(Chess chess, int row, int col) {
        if (Math.abs(row - chess.row) != Math.abs(col - chess.col)) return false;
        if (Math.abs(row - chess.row) != 1) return false;
        return generalMoveLimit(chess, row, col);
    }

    /**
     * 通用移动限制，只能直线或者斜线走1
     */
    public final boolean generalMoveLimitCompleteOneStep(Chess chess, int row, int col) {
        if (Math.abs(row - chess.row) > 1 || Math.abs(col - chess.col) > 1) return false;
        return generalMoveLimit(chess, row, col);
    }

    /**
     * 通用移动限制，不能停留原地，不能移动到有棋子的地方
     */
    public final boolean generalMoveLimit(Chess chess, int row, int col) {
        if (row == chess.row && col == chess.col) { //不能停留在原地
            return false;
        }
        if (curGrids[row][col] != 0) { //不能移动到有棋子的地方
            return false;
        }
        return true; //其它情况都能走
    }

    /**
     * 激励状态
     */
    private static class InspireState {

        boolean isOpen; //状态是否打开状态
        int openStep; //记录被激励的回合

        public InspireState() {
            this.isOpen = false;
            this.openStep = -1;
        }

        /**
         * 更新激励状态
         */
        public void change(ZodiacChessRoom room, boolean isOpen) {
            if (isOpen) {
                open(room);
            }
            else {
                close(room);
            }
        }

        /**
         * 打开状态，并且记录回合数
         */
        public void open(ZodiacChessRoom room) {
            isOpen = true;
            if (room.step != 0) openStep = room.step; //非开局默认设置的才会记录，开机默认设置的时候step刚好为0
        }

        /**
         * 关闭状态，需判断回合数不同时才能关闭成功（避免鸡和鼠同时判断的冲突）
         */
        public void close(ZodiacChessRoom room) {
            if (room.step != openStep) {
                isOpen = false;
            }
        }
    }

    /**
     * 被交换状态
     */
    private static class ExchangeState {
        boolean isOpen; //状态是否打开状态
        int exStep; //记录被交换的回合

        public void open(ZodiacChessRoom room) {
            isOpen = true;
            exStep = room.step;
        }
    }

    /**
     * 被咬状态
     */
    private static class BittenState {
        boolean isOpen; //状态是否打开状态
        int bitStep; //被咬得回合数

        public void open(ZodiacChessRoom room) {
            isOpen = true;
            bitStep = room.step;
        }

        public void attemptClose(ZodiacChessRoom room) {
            if (room.step > bitStep) {
                isOpen = false;
            }
        }
    }

    /**
     * 被吸状态
     */
    private static class SeduceState {
        boolean isOpen; //状态是否打开状态
        int sedStep; //被吸得回合数

        public void open(ZodiacChessRoom room) {
            isOpen = true;
            sedStep = room.step;
        }

        public void attemptClose(ZodiacChessRoom room) {
            if (room.step > sedStep) {
                isOpen = false;
            }
        }
    }

    /**
     * 死亡状态
     */
    private static class DeathState {
        boolean isOpen; //状态是否打开状态
        int deadStep; //记录死亡回合

        public DeathState() {
            this.isOpen = false;
            this.deadStep = -1;
        }

        /**
         * 设置为死亡状态，记录死亡回合
         */
        public void open(ZodiacChessRoom room) {
            isOpen = true;
            deadStep = room.step;
        }
    }

    /**
     * 跳跃状态
     */
    private static class JumpState {
        int lastJumpArr; //本回合上次跳跃的方向 0：无， 1：左上，2：右上，3：左下，4：右下
        int originRow; //本回合跳跃之前最开始的位置
        int originCol; //本回合跳跃之前最开始的位置

        public void reset(int row, int col) {
            this.lastJumpArr = 0;
            this.originRow = row;
            this.originCol = col;
        }

        /**
         * 当前位置是否跳跃回了原点
         */
        public boolean jumpBackToOrigin(int row, int col) {
            return row == originRow && col == originCol;
        }
    }

    /**
     * 携带状态
     */
    private static class CarryState {
        Chess carryingChess;
        boolean isExchange;

        public void carry(Chess chess) {
            carryingChess = chess;
            isExchange = false;
        }

        public void down() {
            carryingChess = null;
            isExchange = false;
        }
    }

    /**
     * 棋子基类
     * 🐀🐂🐅🐇🐉🐍🐎🐏🐒🐓🐕🐖
     */
    private static abstract class Chess {
        public String name; //棋子名称
        public String image;
        public int side; //棋子阵营，1：黑方，2：白方
        public int row; //棋子所在行数
        public int col;//棋子所在列数
        public int recentMoveStep; //记录棋子最近主动移动的回合，未移动过标记为-1
        public DeathState deathState; //死亡状态
        public InspireState inspireState; //激励状态
        public BittenState bittenState; //被咬状态
        public SeduceState seduceState; //被吸状态
        public ExchangeState exchangeState; //被交换状态

        public Chess(String name, String image, int side, int row, int col) {
            this.name = name;
            this.image = image;
            this.side = side;
            this.row = row;
            this.col = col;
            this.recentMoveStep = -1;
            deathState = new DeathState();
            inspireState = new InspireState();
            bittenState = new BittenState();
            seduceState = new SeduceState();
            exchangeState = new ExchangeState();
        }

        /**
         * 是否联手状态?
         * 上下左右有同队棋子时视为联手状态
         * 联手状态不能被除兔以外的棋子移动
         */
        public final boolean isJointState(ZodiacChessRoom room) {
            int maxRow = room.curGrids.length - 1;
            int maxCol = room.curGrids[0].length - 1;
            if (row > 0 && room.curGrids[row - 1][col] == side) return true;
            if (row < maxRow && room.curGrids[row + 1][col] == side) return true;
            if (col > 0 && room.curGrids[row][col - 1] == side) return true;
            if (col < maxCol && room.curGrids[row][col + 1] == side) return true;
            return false;
        }

        /**
         * 获取该棋子可移动范围
         */
        public final List<int[]> getCanMovedPos(ZodiacChessRoom room) {
            List<int[]> canMovedPosList = new ArrayList<>();
            int[][] curGrids = room.curGrids;
            int h = curGrids.length;
            int w = curGrids[0].length;
            for (int i = 0; i < h; i++) {
                for (int j = 0; j < w; j++) {
                    if (isPosCanMoveFace(room, i, j)) {
                        canMovedPosList.add(new int[]{i, j});
                    }
                }
            }
            return canMovedPosList;
        }

        /**
         * 获取棋子文字颜色，黑方红色，白方蓝色
         */
        public final String getColor() {
            if (deathState.isOpen) { //已死亡棋子，颜色变淡
                if (side == 1) {
                    return "#FF996666";
                }
                else if (side == 2) {
                    return "#FF9999EE";
                }
                else {
                    return "#FFAAAA66";
                }
            }
            else {
                if (side == 1) {
                    return "#FFEE6666";
                }
                else if (side == 2) {
                    return "#FF6666EE";
                }
                else {
                    return "#FFEEEE66";
                }
            }
        }

        /**
         * 当被选中时
         */
        public void onChosen(ZodiacChessRoom room) {
            room.setGameTip(room.getCurText() + "移动棋子[" + name + "]");
            room.playerClickGridEventListener = (room1, player, row, col, side) -> {
                if (isPosCanMoveFace(room1, row, col)) { //点击了可移动到的位置时
                    room.waitChooseChesses = null; //一旦有棋子移动了，就不要再指定待选了
                    room.removeCustomButtonClickListener(this.side, "取消"); //取消按钮记得也要移除
                    room.playerClickGridEventListener = null; //一旦移动了就制空点击事件
                    room1.doMoveChessAction(this, row, col); //执行移动操作
                }
                else { //点击了不能移动到的位置
                    room.cancelChoose(); //取消选中状态
                    Chess othChess = room1.getChessByPos(row, col);
                    if (othChess != null && othChess.side == this.side) { //点击了别的己方棋子时
                        if (room.waitChooseChesses != null && !room.waitChooseChesses.contains(othChess)) {
                            room1.setWaitPlayerChooseChess(); //切换回待选状态
                            room1.noticeAllRefreshGameInfo();//记得刷新
                        }
                        else {
                            room1.doChooseChessAction(othChess);//选中别的己方棋子
                            room1.noticeAllRefreshGameInfo();//记得刷新
                        }
                    }
                    else { //点击了不能移动到的空格或者对方棋子时
                        room1.setWaitPlayerChooseChess(); //切换回待选状态
                        room1.noticeAllRefreshGameInfo();//记得刷新
                    }
                }
            };
        }

        /**
         * 当取消选中时 （例如 狗选中时会分发咬得按钮，此时可以用于移除这个）
         */
        public void onCancelChosen(ZodiacChessRoom room) {

        }

        /**
         * 棋子移动后
         */
        public void onMoved(ZodiacChessRoom room, int newRow, int newCol) {
            this.row = newRow;
            this.col = newCol;
            this.recentMoveStep = room.step;
            room.checkMoveToOthMouse(this);//判断是否走到了对方鼠的边上，如果走到了标记对方老鼠死亡
            Mouse othMouse = room.getMouseChess(side == 1 ? 2 : 1);
            if (!othMouse.deathState.isOpen) { //对方老鼠没有死亡，才进行后续的判断
                room.checkSurroundedChesses(this); //判断对方是否有棋子被包围，被包围的棋子全部标记为死亡，并且添加到waitEatChesses中
            }
        }

        /**
         * 棋子被特效移动后
         */
        public void onPut(ZodiacChessRoom room, int pushedRow, int pushedCol) {
            this.row = pushedRow;
            this.col = pushedCol;
            if (this.side == room.cur) { //如果让自己被动移动的是自己回合，那么可以视为主动移动，还要再判断一下是否有死亡
                room.checkMoveToOthMouse(this);//判断是否走到了对方鼠的边上，如果走到了标记对方老鼠死亡
                Mouse othMouse = room.getMouseChess(side == 1 ? 2 : 1);
                if (!othMouse.deathState.isOpen) { //对方老鼠没有死亡，才进行后续的判断
                    room.checkSurroundedChesses(this); //判断对方是否有棋子被包围，被包围的棋子全部标记为死亡，并且添加到waitEatChesses中
                }
            }
        }

        /**
         * 更新棋子状态
         */
        public void updateState(ZodiacChessRoom room) {
            bittenState.attemptClose(room);
            seduceState.attemptClose(room);
        }

        /**
         * 该位置是否可以移动到
         */
        public boolean isPosCanMoveFace(ZodiacChessRoom room, int row, int col) {
            if (this.bittenState.isOpen) return false;
            if (this.seduceState.isOpen) return false;
            return isPosCanMove(room, row, col);
        }

        /**
         * 该位置是否可以移动到 (只被isPosCanMoveFace调用，其它地方调用isPosCanMoveFace)
         */
        public boolean isPosCanMove(ZodiacChessRoom room, int row, int col) {
            return room.generalMoveLimitStraightOneStep(this, row, col);
        }

        /**
         * 该位置是否可以因某个棋子被移动到
         */
        public boolean isPosCanPut(ZodiacChessRoom room, Chess chess, int row, int col) {
            if (this.side != chess.side && (this.isJointState(room)) && !chess.inspireState.isOpen && !(chess instanceof Rabbit)) {
                return false; //该棋子处于联手状态且源棋子是敌人时且没有处于激励状态时，不能被强制移动，并且兔子可以忽视联手的限制
            }
            if (this.bittenState.isOpen) { //被狗咬住的状态下不能被移动
                return false;
            }
            if (row < 0 || row >= room.curGrids.length || col < 0 || col >= room.curGrids[0].length) {
                return false; //强制移动到棋盘外也不允许
            }
            if (room.curGrids[row][col] != 0) {
                return false; //也不能被强制移动到已经有棋子的地方
            }
            return true; //其它情况允许被强制移动
        }


    }

    /**
     * 鼠
     */
    private static class Mouse extends Chess {

        public Mouse(int side, int row, int col) {
            super("鼠", "🐀", side, row, col);
        }

        @Override
        public void updateState(ZodiacChessRoom room) {
            super.updateState(room);
            //鼠走到中线和中线以外时，所有同伴都获得激励状态
            for (Chess chess : room.chessList) {
                if (chess.side == this.side) {
                    if (side == 1) {
                        chess.inspireState.change(room, this.row >= midLineRow);
                    }
                    else if (side == 2) {
                        chess.inspireState.change(room, this.row <= midLineRow + 1);
                    }
                }
            }
        }

        @Override
        public boolean isPosCanMove(ZodiacChessRoom room, int row, int col) {
            if (side == 1) {
                if (this.row == midLineRow) { //黑方贴近中线时
                    //自身左上格和右上格不能走
                    if (row == this.row - 1 && col == this.col - 1) {
                        return false;
                    }
                    else if (row == this.row - 1 && col == this.col + 1) {
                        return false;
                    }
                    return room.generalMoveLimitCompleteOneStep(this, row, col);
                }
                else if (this.row > midLineRow) { //黑方越过中线时
                    return room.generalMoveLimitCompleteOneStep(this, row, col);
                }
                else { //黑方在中线以内时
                    return room.generalMoveLimitStraightOneStep(this, row, col);
                }
            }
            else if (side == 2) {
                if (this.row < midLineRow + 1) { //白方越过中线时
                    return room.generalMoveLimitCompleteOneStep(this, row, col);
                }
                else if (this.row == midLineRow + 1) { //白方贴近中线时
                    //自身左下格和右下格不能走
                    if (row == this.row + 1 && col == this.col - 1) {
                        return false;
                    }
                    else if (row == this.row + 1 && col == this.col + 1) {
                        return false;
                    }
                    return room.generalMoveLimitCompleteOneStep(this, row, col);
                }
                else { //白方在中线以内时
                    return room.generalMoveLimitStraightOneStep(this, row, col);
                }
            }
            return false;
        }
    }

    /**
     * 牛
     */
    private static class Buffalo extends Chess {

        public Buffalo(int side, int row, int col) {
            super("牛", "🐃", side, row, col);
        }

        @Override
        public boolean isPosCanMove(ZodiacChessRoom room, int row, int col) {
            if (Math.abs(row - this.row) + Math.abs(col - this.col) != 1) return false; //只能直线走1格
            Chess pushedChess = room.getChessByPos(row, col); //获取被顶的棋子
            if (pushedChess != null) {
                int pushedRow = pushedChess.row + (row - this.row); //被顶后的新位置
                int pushedCol = pushedChess.col + (col - this.col); //被顶后的新位置
                return pushedChess.isPosCanPut(room, this, pushedRow, pushedCol); //但是后面是空的可顶
            }
            return true;
        }

        @Override
        public void onMoved(ZodiacChessRoom room, int newRow, int newCol) {
            int oldRow = this.row;
            int oldCol = this.col;
            this.recentMoveStep = room.step; //这里要更新一下，（否则鸡会认为你没走过）
            // 牛牛移动前顶棋的操作
            Chess pushedChess = room.getChessByPos(newRow, newCol); //获取被顶的棋子
            if (pushedChess != null) {
                int pushedRow = pushedChess.row + (newRow - oldRow); //被顶后的新位置
                int pushedCol = pushedChess.col + (newCol - oldCol); //被顶后的新位置
                room.doPutChessAction(pushedChess, pushedRow, pushedCol); //无须判断（前置已经判断过了）执行被顶操作
            }
            else {
                //牛牛移动后冲顶的操作
                int collidedRow = newRow + (newRow - oldRow);
                int collidedCol = newCol + (newCol - oldCol);
                int newCollidedRow = collidedRow + (collidedRow - newRow); //被冲撞后的新位置
                int newCollidedCol = collidedCol + (collidedCol - newCol); //被冲撞后的新位置
                Chess collidedChess = room.getChessByPos(collidedRow, collidedCol); //获取被冲撞的棋子
                if (collidedChess != null && collidedChess.isPosCanPut(room, this, newCollidedRow, newCollidedCol)) {
                    room.interruptEndCheckFlag = true; //中断结束判断的执行
                    room.setCustomButtonClickListener(this.side, "冲顶", () -> { //下发动态按钮
                        room.removeCustomButtonClickListener(this.side, "不顶");
                        room.interruptEndCheckFlag = false; //正常冲顶后让中断接结束判断可以继续执行，但是如果collidedChess的doPutChessAction()里又让room.interruptEndCheckFlag = true。那么下方的room.onDoActionOver()任然不会执行到checkGameOver();然而，总有一个节点会是最终结束节点
                        room.doPutChessAction(collidedChess, newCollidedRow, newCollidedCol); //执行冲撞操作
                        //继续执行被中断的结束判断
                        room.roundInteractiveActionsChain.checkRoundOver(); //执行主动移动行为结束判断
                    });
                    room.setCustomButtonClickListener(this.side, "不顶", () -> { //下发动态按钮
                        room.interruptEndCheckFlag = false;
                        room.removeCustomButtonClickListener(this.side, "冲顶");
                        //继续执行被中断的结束判断
                        room.roundInteractiveActionsChain.checkRoundOver(); //执行主动移动行为结束判断
                    });
                }
            }
            super.onMoved(room, newRow, newCol); //通用移动动作回调
        }
    }

    /**
     * 虎
     */
    private static class Tiger extends Chess {

        public Tiger(int side, int row, int col) {
            super("虎", "🐅", side, row, col);
        }

        @Override
        public boolean isPosCanMove(ZodiacChessRoom room, int row, int col) {
            if (row == this.row && col == this.col) return false; //不能停留原地
            if (room.curGrids[row][col] != 0) return false; //不能移动到有棋子的地方
            int rowDiff = Math.abs(row - this.row);
            int colDiff = Math.abs(col - this.col);
            if (rowDiff > 2 || colDiff > 2) return false; //移动的直线距离不能大于2
            if (rowDiff != 0 && colDiff != 0) return false; //只能直线移动
            if (rowDiff == 2 || colDiff == 2) { //走两格时
                if (room.curGrids[(row + this.row) / 2][(col + this.col) / 2] != 0) { //路径中间有其它棋子时
                    return this.inspireState.isOpen; //激励神虎才能跳过
                }
            }
            return true;
        }
    }

    /**
     * 兔
     */
    private static class Rabbit extends Chess {

        public Rabbit(int side, int row, int col) {
            super("兔", "🐇", side, row, col);
        }

        @Override
        public boolean isPosCanMove(ZodiacChessRoom room, int row, int col) {
            return room.generalMoveLimitDiagonalOneStep(this, row, col);
        }

        @Override
        public void onPut(ZodiacChessRoom room, int pushedRow, int pushedCol) {
            super.onPut(room, pushedRow, pushedCol);
            if (this.side == room.cur) checkSeduceMove(room);//兔只有移动（不包括被对方移动）后才能吸
        }


        @Override
        public void onMoved(ZodiacChessRoom room, int newRow, int newCol) {
            super.onMoved(room, newRow, newCol);
            if (this.side == room.cur) checkSeduceMove(room);
        }

        //搜寻所有可吸棋子
        private void checkSeduceMove(ZodiacChessRoom room) {

            List<Chess> canSeduceChess = new ArrayList<>();
            Chess chessA = room.getChessByPos(row - 2, col); //兔只能吸移动后直线第2格的棋子
            boolean canSedA = room.getChessByPos(row - 1, col) == null; //看看和chessA之间是否有阻碍
            if (chessA != null && canSedA && chessA.recentMoveStep != room.step && !chessA.bittenState.isOpen) { //兔只能吸本轮没有动过的子，可无视对方联手,但是吸不了被对方咬的棋子
                canSeduceChess.add(chessA);
            }
            Chess chessB = room.getChessByPos(row + 2, col);
            boolean canSedB = room.getChessByPos(row + 1, col) == null;
            if (chessB != null && canSedB && chessB.recentMoveStep != room.step && !chessB.bittenState.isOpen) {
                canSeduceChess.add(chessB);
            }
            Chess chessC = room.getChessByPos(row, col - 2);
            boolean canSedC = room.getChessByPos(row, col - 1) == null;
            if (chessC != null && canSedC && chessC.recentMoveStep != room.step && !chessC.bittenState.isOpen) {
                canSeduceChess.add(chessC);
            }
            Chess chessD = room.getChessByPos(row, col + 2);
            boolean canSedD = room.getChessByPos(row, col + 1) == null;
            if (chessD != null && canSedD && chessD.recentMoveStep != room.step && !chessD.bittenState.isOpen) {
                canSeduceChess.add(chessD);
            }
            if (canSeduceChess.size() > 0) {
                room.interruptEndCheckFlag = true; //中断默认的结束判断
                room.canSeduceChesses = new ArrayList<>(canSeduceChess);
                //不想吸的话可以点击“结束按钮”
                room.setCustomButtonClickListener(this.side, "结束", () -> {
                    room.canSeduceChesses = null;
                    room.interruptEndCheckFlag = false;
                    room.roundInteractiveActionsChain.checkRoundOver(); //执行主动移动行为结束判断
                });
                //分发点击事件，被点击了的棋子靠近兔，点完了自动结束，并且执行结束判定
                this.setClickListenerWaitSeduce(room);
            }
        }

        private void setClickListenerWaitSeduce(ZodiacChessRoom room) {
            StringBuilder ns = new StringBuilder();
            for (Chess seduceChess : room.canSeduceChesses) {
                ns.append("[").append(seduceChess.name).append("]");
            }
            room.gameTip = room.getCurText() + "吸" + ns;
            room.playerClickGridEventListener = (room1, player, row, col, side) -> {
                Chess chess = room1.getChessByPos(row, col);
                if (room.canSeduceChesses.contains(chess)) {
                    this.recentMoveStep = room.step; //兔执行了吸了，就标记最近移动过了
                    if (chess.side != this.side) chess.seduceState.open(room1); //对方棋子才标记被吸状态 （被吸的下回合不能主动移动）
                    room.doPutChessAction(chess, (chess.row + this.row) / 2, (chess.col + this.col) / 2); //被吸靠近兔子身旁
                    room.canSeduceChesses.remove(chess);
                    room.checkMoveToOthMouse(this); //判断是否吸死鼠了
                    Mouse othMouse = room.getMouseChess(side == 1 ? 2 : 1);
                    if (!othMouse.deathState.isOpen && room.canSeduceChesses.size() > 0) { //对方老鼠没有死亡，才进行后续的判断
                        this.setClickListenerWaitSeduce(room1);
                    }
                    else { //吸死对方鼠或者全部可吸的都被吸完了，那么自动结束
                        room1.interruptEndCheckFlag = false;
                        room1.canSeduceChesses = null;
                        room1.removeCustomButtonClickListener(this.side, "结束");
                        room1.roundInteractiveActionsChain.checkRoundOver();
                    }
                    room1.noticeAllRefreshGameInfo();
                }
            };
            room.noticeAllRefreshGameInfo();
        }
    }

    /**
     * 龙
     */
    private static class Loong extends Chess {

        public Loong(int side, int row, int col) {
            super("龙", "🐉", side, row, col);
        }

        @Override
        public boolean isPosCanMove(ZodiacChessRoom room, int row, int col) {
            Chess exchangeChess = room.getChessByPos(row, col);
            if (exchangeChess != null && exchangeChess != this) { //目的地有棋子时判断是否可以交换
                if (this.side != exchangeChess.side && (exchangeChess.isJointState(room)) && !this.inspireState.isOpen)
                    return false; //因为优先级问题，不能被交换
                if (exchangeChess instanceof Loong && this.exchangeState.exStep == room.step - 1)
                    return false; //刚被对方龙交换时，不能立刻交换对方龙回去
                if (exchangeChess.bittenState.isOpen)
                    return false; //目标刚被狗咬住，不能用龙换回去
                return Math.abs(exchangeChess.row - this.row) + Math.abs(exchangeChess.col - this.col) == 1; //两个棋子相邻才能交换
                //return room.isPosStraightMeet(exchangeChess.row, exchangeChess.col, this.row, this.col);  //两个棋子直线相望时才能交换
            }
            else { //目的地没有棋子时
                return room.generalMoveLimitCompleteOneStep(this, row, col); //走王步
            }
        }

        @Override
        public void onMoved(ZodiacChessRoom room, int newRow, int newCol) {
            Chess pushedChess = room.getChessByPos(newRow, newCol); //获取被交换的棋子
            if (pushedChess != null) {
                int oldRow = this.row;
                int oldCol = this.col;
                this.row = newRow;  //这里提前修改，以免被交换的棋子判断包围出错
                this.col = newCol; //这里提前修改，以免被交换的棋子判断包围出错
                this.recentMoveStep = room.step;
                pushedChess.exchangeState.open(room); //记录被交换状态
                room.doPutChessAction(pushedChess, oldRow, oldCol); //无须判断（前置已经判断过了）执行被交换操作
            }
            super.onMoved(room, newRow, newCol);
        }
    }

    /**
     * 蛇
     */
    private static class Snake extends Chess {

        public Snake(int side, int row, int col) {
            super("蛇", "🐍", side, row, col);
        }

        @Override
        public boolean isPosCanMove(ZodiacChessRoom room, int row, int col) {
            if (!room.isPosDiagonalMeet(row, col, this.row, this.col)) return false; //两个位置非对角相望时不能走到
            return room.generalMoveLimit(this, row, col);
        }
    }

    /**
     * 马
     */
    private static class Horse extends Chess {

        public Horse(int side, int row, int col) {
            super("马", "🐎", side, row, col);
        }

        @Override
        public boolean isPosCanMove(ZodiacChessRoom room, int row, int col) {
            int rowDiff = Math.abs(row - this.row);
            int colDiff = Math.abs(col - this.col);
            if (rowDiff == 1 && colDiff == 2 || rowDiff == 2 && colDiff == 1) {
                return room.generalMoveLimit(this, row, col);
            }
            else {
                return false;
            }
        }
    }

    /**
     * 羊
     */
    private static class Sheep extends Chess {

        private JumpState jumpState; //跳跃状态

        public Sheep(int side, int row, int col) {
            super("羊", "🐏", side, row, col);
            jumpState = new JumpState();
            jumpState.reset(row, col);
        }

        @Override
        public void onChosen(ZodiacChessRoom room) {
            jumpState.reset(this.row, this.col); //每次初被玩家选择时肯定是新的决策，要把记录恢复为0
            super.onChosen(room);
        }

        @Override
        public boolean isPosCanMove(ZodiacChessRoom room, int row, int col) {
            if (jumpState.lastJumpArr == 0) { //本回合没移动过，可以直走可以跳
                if (Math.abs(row - this.row) + Math.abs(col - this.col) != 1) { //非正常走一格的情况下
                    return isCanJumpPos(room, row, col); //判断判断是否能跳过去
                }
                else {
                    return super.isPosCanMove(room, row, col); //正常直线移动一格
                }
            }
            else { //本回合移动过，只可以跳
                return isCanJumpPos(room, row, col);
            }
        }

        //该位置是否和上一步跳的方向相同
        private boolean isJumpSameDir(ZodiacChessRoom room, int row, int col) {
            if (jumpState.lastJumpArr == 0) return true;
            if (jumpState.lastJumpArr == 1 && row < this.row && col < this.col) return true;
            if (jumpState.lastJumpArr == 2 && row < this.row && col > this.col) return true;
            if (jumpState.lastJumpArr == 3 && row > this.row && col < this.col) return true;
            if (jumpState.lastJumpArr == 4 && row > this.row && col > this.col) return true;
            return false;
        }

        //判断该位置是否可以跳跃过去
        private boolean isCanJumpPos(ZodiacChessRoom room, int row, int col) {
            if (row < 0 || row > room.curGrids.length - 1 || col < 0 || col > room.curGrids[0].length - 1)
                return false; //棋盘范围之外不可跳
            if (room.curGrids[row][col] != 0) return false; //有棋子的位置不可跳
            int rDif = Math.abs(row - this.row);
            int cDif = Math.abs(col - this.col);
            if (rDif != cDif || rDif < 2) return false; //非斜线不可跳，非间隔1格不可跳
            if (!this.inspireState.isOpen && !isJumpSameDir(room, row, col)) return false; //非激励神猴，只能顺着一个方向跳
            if (!this.inspireState.isOpen && rDif > 2) return false; //非激励神猴，不能跳过超过一个棋子
            if (rDif > 2) { //大于2格时
                if (!room.isPosDiagonalConnect(row, col, this.row, this.col)) return false;
            }
            else { //等于2格时
                if (room.curGrids[(this.row + row) / 2][(this.col + col) / 2] == 0) return false; //两格之间没有棋子不可跳
            }
            return true;
        }

        //更新记录上一步跳跃的方向
        private void updateLastJumpArr(int newRow, int newCol) {
            int rDif = Math.abs(newRow - this.row);
            int cDif = Math.abs(newCol - this.col);
            if (rDif == 1 || cDif == 1) jumpState.lastJumpArr = 0;
            else if (newRow < this.row && newCol < this.col) jumpState.lastJumpArr = 1;
            else if (newRow < this.row && newCol > this.col) jumpState.lastJumpArr = 2;
            else if (newRow > this.row && newCol < this.col) jumpState.lastJumpArr = 3;
            else if (newRow > this.row && newCol > this.col) jumpState.lastJumpArr = 4;
        }

        @Override
        public void onMoved(ZodiacChessRoom room, int newRow, int newCol) {
            updateLastJumpArr(newRow, newCol); //更新记录跳跃方向
            if (jumpState.lastJumpArr == 0) { //是走
                super.onMoved(room, newRow, newCol);
            }
            else { //是跳
                room.curGrids[this.row][this.col] = 0; //这里就要更新棋盘，因为 this.getCanMovedPos(room)中会用 room.curGrids[row][col] != 0; 来判断位置是否可跳跃到
                this.row = newRow;
                this.col = newCol;
                room.interruptEndCheckFlag = true; //中断结束判断
                if (this.getCanMovedPos(room).size() > 0) { //还能继续跳
                    //下发动态按钮玩家可以结束跳跃状态 (注意跳回到原点就不能停下了)
                    if (!jumpState.jumpBackToOrigin(row, col)) {
                        room.setCustomButtonClickListener(this.side, "停下", () -> {
                            super.onMoved(room, newRow, newCol);
                            room.choose = null;
                            room.interruptEndCheckFlag = false;
                            //继续执行被中断的结束判断
                            room.roundInteractiveActionsChain.checkRoundOver(); //执行主动移动行为结束判断
                        });
                    }
                    else {
                        room.removeCustomButtonClickListener(this.side, "停下");//移除动态下发的按钮
                    }
                    //分发选择跳跃位置的
                    room.gameTip = room.getCurText() + "选择[羊]的跳跃落点";
                    room.choose = this;
                    room.playerClickGridEventListener = (room1, player, row, col, side) -> {
                        if (isPosCanMoveFace(room1, row, col)) { //点击了可跳跃到的位置时
                            room.playerClickGridEventListener = null;
                            room1.doMoveChessAction(this, row, col);
                        }
                    };
                    room.noticeAllRefreshGameInfo(); //记得刷新
                }
                else { //不能继续跳跃了
                    super.onMoved(room, newRow, newCol);
                    room.removeCustomButtonClickListener(this.side, "停下");//移除动态下发的按钮
                    room.interruptEndCheckFlag = false; //取消中断标记,继续执行被中断的结束判断
                }
            }
        }
    }

    /**
     * 猴
     */
    private static class Monkey extends Chess {

        private JumpState jumpState; //跳跃状态

        public Monkey(int side, int row, int col) {
            super("猴", "🐒", side, row, col);
            jumpState = new JumpState();
            jumpState.reset(row, col);
        }

        @Override
        public void onChosen(ZodiacChessRoom room) {
            jumpState.reset(this.row, this.col); //每次初被玩家选择时肯定是新的决策，要把记录恢复为0
            super.onChosen(room);
        }

        @Override
        public boolean isPosCanMove(ZodiacChessRoom room, int row, int col) {
            if (jumpState.lastJumpArr == 0) { //本回合没移动过，可以直走可以跳
                if (Math.abs(row - this.row) + Math.abs(col - this.col) != 1) { //非正常走一格的情况下
                    return isCanJumpPos(room, row, col); //判断判断是否能跳过去
                }
                else {
                    return super.isPosCanMove(room, row, col); //正常直线移动一格
                }
            }
            else { //本回合移动过，只可以跳
                return isCanJumpPos(room, row, col);
            }
        }

        //该位置是否和上一步跳的方向相同
        private boolean isJumpSameDir(ZodiacChessRoom room, int row, int col) {
            if (jumpState.lastJumpArr == 0) return true;
            if (jumpState.lastJumpArr == 1 && row == this.row - 2 && col == this.col) return true;
            if (jumpState.lastJumpArr == 2 && row == this.row + 2 && col == this.col) return true;
            if (jumpState.lastJumpArr == 3 && row == this.row && col == this.col - 2) return true;
            if (jumpState.lastJumpArr == 4 && row == this.row && col == this.col + 2) return true;
            return false;
        }

        //判断该位置是否可以跳跃过去
        private boolean isCanJumpPos(ZodiacChessRoom room, int row, int col) {
            if (row < 0 || row > room.curGrids.length - 1 || col < 0 || col > room.curGrids[0].length - 1)
                return false; //棋盘范围之外不可跳
            if (room.curGrids[row][col] != 0) return false; //有棋子的位置不可跳
            int rDif = Math.abs(row - this.row);
            int cDif = Math.abs(col - this.col);
            if (!(rDif == 2 && cDif == 0) && !(rDif == 0 && cDif == 2)) return false; //非直线第二格不可跳
            if (room.curGrids[(this.row + row) / 2][(this.col + col) / 2] == 0) return false; //两格之间没有棋子不可跳
            if (!this.inspireState.isOpen && !isJumpSameDir(room, row, col)) return false; //非激励神猴，只能顺着一个方向跳
            return true;
        }

        //更新记录上一步跳跃的方向
        private void updateLastJumpArr(int newRow, int newCol) {
            int rDif = Math.abs(newRow - this.row);
            int cDif = Math.abs(newCol - this.col);
            if (rDif == 1 || cDif == 1) jumpState.lastJumpArr = 0;
            else if (newRow < this.row) jumpState.lastJumpArr = 1;
            else if (newRow > this.row) jumpState.lastJumpArr = 2;
            else if (newCol < this.col) jumpState.lastJumpArr = 3;
            else if (newCol > this.col) jumpState.lastJumpArr = 4;
        }

        @Override
        public void onMoved(ZodiacChessRoom room, int newRow, int newCol) {
            updateLastJumpArr(newRow, newCol); //更新记录跳跃方向
            if (jumpState.lastJumpArr == 0) { //是走
                super.onMoved(room, newRow, newCol);
            }
            else { //是跳
                room.curGrids[this.row][this.col] = 0; //这里就要更新棋盘，因为 this.getCanMovedPos(room)中会用 room.curGrids[row][col] != 0; 来判断位置是否可跳跃到
                this.row = newRow;
                this.col = newCol;
                room.interruptEndCheckFlag = true; //中断结束判断
                if (this.getCanMovedPos(room).size() > 0) { //还能继续跳
                    //下发动态按钮玩家可以结束跳跃状态 (跳回原位置了就不分发以免停留在原地)
                    if (!jumpState.jumpBackToOrigin(this.row, this.col)) {
                        room.setCustomButtonClickListener(this.side, "停下", () -> {
                            super.onMoved(room, newRow, newCol);
                            room.choose = null;
                            room.interruptEndCheckFlag = false;
                            //继续执行被中断的结束判断
                            room.roundInteractiveActionsChain.checkRoundOver(); //执行主动移动行为结束判断
                        });
                    }
                    else {
                        room.removeCustomButtonClickListener(this.side, "停下");//移除动态下发的按钮
                    }
                    //分发选择跳跃位置的
                    room.gameTip = room.getCurText() + "选择[猴]的跳跃落点";
                    room.choose = this;
                    room.playerClickGridEventListener = (room1, player, row, col, side) -> {
                        if (isPosCanMoveFace(room1, row, col)) { //点击了可跳跃到的位置时
                            room.playerClickGridEventListener = null;
                            room1.doMoveChessAction(this, row, col);
                        }
                    };
                    room.noticeAllRefreshGameInfo(); //记得刷新
                }
                else { //不能继续跳跃了
                    super.onMoved(room, newRow, newCol);
                    room.removeCustomButtonClickListener(this.side, "停下");//移除动态下发的按钮
                    room.interruptEndCheckFlag = false; //取消中断标记,继续执行被中断的结束判断
                }
            }
        }
    }

    /**
     * 鸡
     */
    private static class Chicken extends Chess {

        public Chicken(int side, int row, int col) {
            super("鸡", "🐓", side, row, col);
        }

        @Override
        public void updateState(ZodiacChessRoom room) {
            super.updateState(room);
            //先所有己方棋子都取消激励
            for (Chess chess : room.chessList) {
                if (chess.side == this.side) {
                    chess.inspireState.change(room, false);
                }
            }
            //再鸡四周的己方棋子获得激励
            Chess chessA = room.getChessByPos(row - 1, col);
            if (chessA != null) {
                chessA.inspireState.change(room, chessA.side == this.side);
            }
            Chess chessB = room.getChessByPos(row + 1, col);
            if (chessB != null) {
                chessB.inspireState.change(room, chessB.side == this.side);
            }
            Chess chessC = room.getChessByPos(row, col - 1);
            if (chessC != null) {
                chessC.inspireState.change(room, chessC.side == this.side);
            }
            Chess chessD = room.getChessByPos(row, col + 1);
            if (chessD != null) {
                chessD.inspireState.change(room, chessD.side == this.side);
            }
        }

        @Override
        public void onPut(ZodiacChessRoom room, int pushedRow, int pushedCol) {
            super.onPut(room, pushedRow, pushedCol);
            if (this.side == room.cur) checkChainMove(room); //己方棋子使自己鸡移动时才算主动移动，才可能判断连锁移动
        }

        @Override
        public void onMoved(ZodiacChessRoom room, int newRow, int newCol) {
            super.onMoved(room, newRow, newCol);
            if (this.side == room.cur) checkChainMove(room);
        }

        //搜寻所有可以连锁移动的己方棋子列表，鸡移动后，鸡的上下左右相邻的一个，本回合没有移动过的己方棋子
        private void checkChainMove(ZodiacChessRoom room) {
            List<Chess> chainMoveChess = new ArrayList<>();
            Chess chessA = room.getChessByPos(row - 1, col);
            if (chessA != null && chessA.side == this.side && chessA.recentMoveStep != room.step) {
                chainMoveChess.add(chessA);
            }
            Chess chessB = room.getChessByPos(row + 1, col);
            if (chessB != null && chessB.side == this.side && chessB.recentMoveStep != room.step) {
                chainMoveChess.add(chessB);
            }
            Chess chessC = room.getChessByPos(row, col - 1);
            if (chessC != null && chessC.side == this.side && chessC.recentMoveStep != room.step) {
                chainMoveChess.add(chessC);
            }
            Chess chessD = room.getChessByPos(row, col + 1);
            if (chessD != null && chessD.side == this.side && chessD.recentMoveStep != room.step) {
                chainMoveChess.add(chessD);
            }
            if (chainMoveChess.size() > 0) {
                this.chainInspire(room); //鸡移动就更新所有棋子激励状态但是不记录回合，以免被连锁移动的棋子远离鸡的时候 激励状态不能取消
                room.interruptEndCheckFlag = true; //中断默认的结束判断
                RoundInteractiveActionsChain chainMoveAction = new RoundInteractiveActionsChain(room, room.getCurPlayer());
                List<CustomBottom> customBottomList = new ArrayList<>();
                customBottomList.add(new CustomBottom(room, "取消", player -> {
                    room.cancelChoose(); //取消选中状态
                    room.interruptEndCheckFlag = false;
                    room.waitChooseChesses = null;
                    room.removeCustomButtonClickListener(side, "取消");
                    room.roundInteractiveActionsChain.checkRoundOver(); //执行主动移动行为结束判断
                }));
                chainMoveAction.setCustomBottomList(customBottomList);
                room.waitChooseChesses = new ArrayList<>(chainMoveChess);
                StringBuilder ns = new StringBuilder();
                for (int i = 0; i < room.waitChooseChesses.size(); i++) {
                    ns.append(room.waitChooseChesses.get(i).name);
                    if (i < room.waitChooseChesses.size() - 1) ns.append(",");
                }
                chainMoveAction.setActionGameTip("选择[" + ns + "]连锁移动");
                chainMoveAction.setPlayerClickGridEventListener(room.roundInteractiveActionsChain.getPlayerClickGridEventListener());
                room.roundInteractiveActionsChain.setNextNode(chainMoveAction);
            }
        }

        //连锁时的激励不记录时间，以避免连锁移动棋子远离鸡的时候不能取消激励
        private void chainInspire(ZodiacChessRoom room) {
            //先所有己方棋子都取消激励
            for (Chess chess : room.chessList) {
                if (chess.side == this.side) {
                    chess.inspireState.change(room, false);
                }
            }
            //再鸡四周的己方棋子获得激励
            Chess chessA = room.getChessByPos(row - 1, col);
            if (chessA != null) {
                chessA.inspireState.isOpen = chessA.side == this.side;
            }
            Chess chessB = room.getChessByPos(row + 1, col);
            if (chessB != null) {
                chessB.inspireState.isOpen = chessB.side == this.side;
            }
            Chess chessC = room.getChessByPos(row, col - 1);
            if (chessC != null) {
                chessC.inspireState.isOpen = chessC.side == this.side;
            }
            Chess chessD = room.getChessByPos(row, col + 1);
            if (chessD != null) {
                chessD.inspireState.isOpen = chessD.side == this.side;
            }
        }
    }

    /**
     * 狗
     */
    private static class Dog extends Chess {

        private int recentBitStep; //最近一次使用过咬技能得回合，狗子不能连续使用咬

        public Dog(int side, int row, int col) {
            super("狗", "🐕", side, row, col);
            recentBitStep = -99;
        }

        @Override
        public boolean isPosCanMove(ZodiacChessRoom room, int row, int col) {
            if (Math.abs(row - this.row) + Math.abs(col - this.col) == 1) {
                return room.generalMoveLimit(this, row, col);
            }
            else {
                if (canAroundMove(room, row, col)) {
                    return room.generalMoveLimit(this, row, col);
                }
            }
            return false;
        }

        //狗可以绕过对方棋子走到对方棋子旁边
        private boolean canAroundMove(ZodiacChessRoom room, int row, int col) {
            if (row == this.row - 1 && (col == this.col - 1 || col == this.col + 1)) {
                Chess chess = room.getChessByPos(this.row - 1, this.col);
                if (chess != null && chess.side != this.side) return true;
            }
            if (row == this.row + 1 && (col == this.col - 1 || col == this.col + 1)) {
                Chess chess = room.getChessByPos(this.row + 1, this.col);
                if (chess != null && chess.side != this.side) return true;
            }
            if (col == this.col - 1 && (row == this.row - 1 || row == this.row + 1)) {
                Chess chess = room.getChessByPos(this.row, this.col - 1);
                if (chess != null && chess.side != this.side) return true;
            }
            if (col == this.col + 1 && (row == this.row - 1 || row == this.row + 1)) {
                Chess chess = room.getChessByPos(this.row, this.col + 1);
                if (chess != null && chess.side != this.side) return true;
            }
            return false;
        }

        //获取前面一个棋子
        private Chess getFrontChess(ZodiacChessRoom room) {
            return this.side == 1 ? room.getChessByPos(this.row + 1, this.col) : room.getChessByPos(this.row - 1, this.col);
        }

        //获取后面一个棋子
        private Chess getBackChess(ZodiacChessRoom room) {
            return this.side == 1 ? room.getChessByPos(this.row - 1, this.col) : room.getChessByPos(this.row + 1, this.col);
        }

        //获取左面一个棋子
        private Chess getLeftChess(ZodiacChessRoom room) {
            return room.getChessByPos(this.row, this.col - 1);
        }

        //获取右面一个棋子
        private Chess getRightChess(ZodiacChessRoom room) {
            return room.getChessByPos(this.row, this.col + 1);
        }

        //是否可以后退
        private boolean canMoveBack(ZodiacChessRoom room) {
            if (this.bittenState.isOpen) return false; //狗自己被狗咬住了也不能动啊
            if (this.seduceState.isOpen) return false; //狗自己被兔吸住了也不能动啊
            if (side == 1) {
                if (this.row == 0) return false;
                else if (room.getChessByPos(this.row - 1, this.col) != null) return false;
            }
            else {
                if (this.row == room.curGrids.length - 1) return false;
                else if (room.getChessByPos(this.row + 1, this.col) != null) return false;
            }
            return true;
        }

        //是否可以前进
        private boolean canMoveFront(ZodiacChessRoom room) {
            if (this.bittenState.isOpen) return false; //狗自己被狗咬住了也不能动啊
            if (this.seduceState.isOpen) return false; //狗自己被兔吸住了也不能动啊
            if (side == 1) {
                if (this.row == room.curGrids.length - 1) return false;
                else if (room.getChessByPos(this.row + 1, this.col) != null) return false;
            }
            else {
                if (this.row == 0) return false;
                else if (room.getChessByPos(this.row - 1, this.col) != null) return false;
            }
            return true;
        }

        //是否可以左移
        private boolean canMoveLeft(ZodiacChessRoom room) {
            if (this.bittenState.isOpen) return false; //狗自己被狗咬住了也不能动啊
            if (this.seduceState.isOpen) return false; //狗自己被兔吸住了也不能动啊
            if (this.col == 0) return false;
            else if (room.getChessByPos(this.row, this.col - 1) != null) return false;
            return true;
        }

        //是否可以右移
        private boolean canMoveRight(ZodiacChessRoom room) {
            if (this.bittenState.isOpen) return false; //狗自己被狗咬住了也不能动啊
            if (this.seduceState.isOpen) return false; //狗自己被兔吸住了也不能动啊
            if (this.col == room.curGrids[0].length - 1) return false;
            else if (room.getChessByPos(this.row, this.col + 1) != null) return false;
            return true;
        }

        //是否可以咬？狗子不能连续使用咬
        private boolean isCanBit(ZodiacChessRoom room) {
            return room.step - this.recentBitStep > 2;
        }

        //取消咬按钮
        private void cancelBitButtons(ZodiacChessRoom room) {
            Chess fChess = getFrontChess(room);
            if (fChess != null) {
                room.removeCustomButtonClickListener(side, "咬[" + fChess.name + "]");
            }
            Chess bChess = getBackChess(room);
            if (bChess != null) {
                room.removeCustomButtonClickListener(side, "咬[" + bChess.name + "]");
            }
            Chess lChess = getLeftChess(room);
            if (lChess != null) {
                room.removeCustomButtonClickListener(side, "咬[" + lChess.name + "]");
            }
            Chess rChess = getRightChess(room);
            if (rChess != null) {
                room.removeCustomButtonClickListener(side, "咬[" + rChess.name + "]");
            }
        }

        @Override
        public void onChosen(ZodiacChessRoom room) {
            Chess fChess = getFrontChess(room); //获取狗前方的一个棋子
            boolean canMoveBack = canMoveBack(room); //是否可以后退
            if (canMoveBack && fChess != null) {  //若有可咬棋子，且咬cd好了
                boolean priorityCanBit = fChess.side == this.side || !fChess.isJointState(room) || this.inspireState.isOpen;//优先级是否合格：咬对方时，对方棋子如果处于联手状态，狗必须是激励神狗才能咬
                boolean beOtherBit = fChess.side == this.side && fChess.bittenState.isOpen; //该棋子是己方棋子且被对方狗咬住了,不能咬
                if (priorityCanBit && isCanBit(room) && !beOtherBit) { //优先级和狗自身咬技能的cd都好了
                    room.setCustomButtonClickListener(side, "咬[" + fChess.name + "]", () -> {  //下发咬这个动态按钮
                        cancelBitButtons(room); //取消其他的狗的咬按钮
                        int oldRow = this.row;
                        int oldCol = this.col;
                        int backRow = side == 1 ? this.row - 1 : this.row + 1;
                        this.row = backRow;//提前移动以免被动棋子逻辑错误
                        this.recentBitStep = room.step; //狗子自己记录最近咬得回合，进入CD时间
                        this.recentMoveStep = room.step;//咬也算移动了也要记录，以鸡等棋子的连锁判断出错
                        if (fChess.side != this.side) fChess.bittenState.open(room); //对方棋子更新被咬住了的状态
                        room.waitChooseChesses = null; //万一狗是被鸡连锁移动，那么狗也要把这个待选状态清除
                        room.removeCustomButtonClickListener(this.side, "取消"); //取消按钮记得也要移除
                        room.doPutChessAction(fChess, oldRow, oldCol); //执行咬退操作
                        room.doMoveChessAction(this, backRow, col); //执行移动操作
                    });
                }
            }

            Chess bChess = getBackChess(room); //获取狗后方的一个棋子
            boolean canMoveFront = canMoveFront(room); //是否可以前进
            if (canMoveFront && bChess != null) {  //若有可咬棋子，且咬cd好了
                boolean priorityCanBit = bChess.side == this.side || !bChess.isJointState(room) || this.inspireState.isOpen;//优先级是否合格：咬对方时，对方棋子如果处于联手状态，狗必须是激励神狗才能咬
                boolean beOtherBit = bChess.side == this.side && bChess.bittenState.isOpen; //该棋子是己方棋子且被对方狗咬住了,不能咬
                if (priorityCanBit && isCanBit(room) && !beOtherBit) { //优先级和狗自身咬技能的cd都好了
                    room.setCustomButtonClickListener(side, "咬[" + bChess.name + "]", () -> {  //下发咬这个动态按钮
                        cancelBitButtons(room); //取消其他的狗的咬按钮
                        int oldRow = this.row;
                        int oldCol = this.col;
                        int frontRow = side == 1 ? this.row + 1 : this.row - 1;
                        this.row = frontRow; //提前移动以免被动棋子逻辑错误
                        this.recentBitStep = room.step; //狗子自己记录最近咬得回合，进入CD时间
                        this.recentMoveStep = room.step;//咬也算移动了也要记录，以鸡等棋子的连锁判断出错
                        if (bChess.side != this.side) bChess.bittenState.open(room); //对方棋子更新被咬住了的状态
                        room.waitChooseChesses = null; //万一狗是被鸡连锁移动，那么狗也要把这个待选状态清除
                        room.removeCustomButtonClickListener(this.side, "取消"); //取消按钮记得也要移除
                        room.doPutChessAction(bChess, oldRow, oldCol); //执行咬退操作
                        room.doMoveChessAction(this, frontRow, col); //执行移动操作
                    });
                }
            }

            Chess rChess = getRightChess(room);
            boolean canMoveLeft = canMoveLeft(room);
            if (canMoveLeft && rChess != null) {  //若有可咬棋子，且咬cd好了
                boolean priorityCanBit = rChess.side == this.side || !rChess.isJointState(room) || this.inspireState.isOpen;//优先级是否合格：咬对方时，对方棋子如果处于联手状态，狗必须是激励神狗才能咬
                boolean beOtherBit = rChess.side == this.side && rChess.bittenState.isOpen; //该棋子是己方棋子且被对方狗咬住了,不能咬
                if (priorityCanBit && isCanBit(room) && !beOtherBit) { //优先级和狗自身咬技能的cd都好了
                    room.setCustomButtonClickListener(side, "咬[" + rChess.name + "]", () -> {  //下发咬这个动态按钮
                        cancelBitButtons(room); //取消其他的狗的咬按钮
                        int oldRow = this.row;
                        int oldCol = this.col;
                        int leftCol = this.col - 1;
                        this.col = leftCol; //提前移动以免被动棋子逻辑错误
                        this.recentBitStep = room.step; //狗子自己记录最近咬得回合，进入CD时间
                        this.recentMoveStep = room.step;//咬也算移动了也要记录，以鸡等棋子的连锁判断出错
                        if (rChess.side != this.side) rChess.bittenState.open(room); //对方棋子更新被咬住了的状态
                        room.waitChooseChesses = null; //万一狗是被鸡连锁移动，那么狗也要把这个待选状态清除
                        room.removeCustomButtonClickListener(this.side, "取消"); //取消按钮记得也要移除
                        room.doPutChessAction(rChess, oldRow, oldCol); //执行咬退操作
                        room.doMoveChessAction(this, row, leftCol); //执行移动操作
                    });
                }
            }

            Chess lChess = getLeftChess(room);
            boolean canMoveRight = canMoveRight(room);
            if (canMoveRight && lChess != null) {  //若有可咬棋子，且咬cd好了
                boolean priorityCanBit = lChess.side == this.side || !lChess.isJointState(room) || this.inspireState.isOpen;//优先级是否合格：咬对方时，对方棋子如果处于联手状态，狗必须是激励神狗才能咬
                boolean beOtherBit = lChess.side == this.side && lChess.bittenState.isOpen; //该棋子是己方棋子且被对方狗咬住了,不能咬
                if (priorityCanBit && isCanBit(room) && !beOtherBit) { //优先级和狗自身咬技能的cd都好了
                    room.setCustomButtonClickListener(side, "咬[" + lChess.name + "]", () -> {  //下发咬这个动态按钮
                        cancelBitButtons(room); //取消其他的狗的咬按钮
                        int oldRow = this.row;
                        int oldCol = this.col;
                        int rightCol = this.col + 1;
                        this.col = rightCol;//提前移动以免被动棋子逻辑错误
                        this.recentBitStep = room.step; //狗子自己记录最近咬得回合，进入CD时间
                        this.recentMoveStep = room.step;//咬也算移动了也要记录，以鸡等棋子的连锁判断出错
                        if (lChess.side != this.side) lChess.bittenState.open(room); //对方棋子更新被咬住了的状态
                        room.waitChooseChesses = null; //万一狗是被鸡连锁移动，那么狗也要把这个待选状态清除
                        room.removeCustomButtonClickListener(this.side, "取消"); //取消按钮记得也要移除
                        room.doPutChessAction(lChess, oldRow, oldCol); //执行咬退操作
                        room.doMoveChessAction(this, row, rightCol); //执行移动操作
                    });
                }
            }

            super.onChosen(room); //正常移动能力也要注册
        }

        @Override
        public void onCancelChosen(ZodiacChessRoom room) {
            cancelBitButtons(room);
        }

        @Override
        public void onMoved(ZodiacChessRoom room, int newRow, int newCol) {
            cancelBitButtons(room); //正常移动过后要取消咬的按钮
            super.onMoved(room, newRow, newCol);
        }
    }

    /**
     * 猪
     */
    private static class Pig extends Chess {

        private CarryState carryState; //携带状态

        public Pig(int side, int row, int col) {
            super("猪", "🐖", side, row, col);
            carryState = new CarryState();
        }

        //猪携带了棋子，并且这两个棋子上下左右都没有别的棋子，就可以交换
        private boolean isCanExchange(ZodiacChessRoom room) {
            if (carryState.carryingChess == null || carryState.isExchange) return false;
            if (room.getChessByPos(carryState.carryingChess.row - 1, carryState.carryingChess.col) != null)
                return false;
            if (room.getChessByPos(carryState.carryingChess.row + 1, carryState.carryingChess.col) != null)
                return false;
            if (room.getChessByPos(this.row - 1, this.col) != null) return false;
            if (room.getChessByPos(this.row + 1, this.col) != null) return false;
            Chess cL = room.getChessByPos(carryState.carryingChess.row, carryState.carryingChess.col - 1);
            if (cL != null && cL != this) return false;
            Chess cR = room.getChessByPos(carryState.carryingChess.row, carryState.carryingChess.col + 1);
            if (cR != null && cR != this) return false;
            Chess tL = room.getChessByPos(this.row, this.col - 1);
            if (tL != null && tL != carryState.carryingChess) return false;
            Chess tR = room.getChessByPos(this.row, this.col + 1);
            if (tR != null && tR != carryState.carryingChess) return false;
            return true;
        }

        //分发携带按钮
        private void putCarryButton(ZodiacChessRoom room) {
            if (this.bittenState.isOpen) return; //猪被咬的状态下不能主动移动
            if (this.seduceState.isOpen) return; //猪被吸的状态下不能主动移动
            Chess leftChess = room.getChessByPos(this.row, this.col - 1); //获取左侧一个棋子
            Chess rightChess = room.getChessByPos(this.row, this.col + 1); //获取右侧一个棋子
            if (leftChess != null) {
                boolean priorityCanCarry = leftChess.side == this.side || !leftChess.isJointState(room) || this.inspireState.isOpen;//优先级是否合格：背对方时，对方棋子如果处于联手状态，猪必须是激励神猪才能背
                boolean beOtherBit = leftChess.side == this.side && leftChess.bittenState.isOpen; //该棋子是己方棋子且被对方狗咬住了,不能咬
                if (priorityCanCarry && !beOtherBit) {
                    room.setCustomButtonClickListener(side, "携带[" + leftChess.name + "]", () -> {  //下发带这个动态按钮
                        carryState.carry(leftChess); //携带左侧棋子
                        cancelCarryButton(room); //取消分发的携带按钮
                        room.setGameTip(room.getCurText() + "移动棋子[猪-" + carryState.carryingChess.name + "]");
                        room.noticeAllRefreshGameInfo();
                        if (isCanExchange(room)) {
                            putBeforeMoveExchangeButton(room); //分发移动之前的可交换按钮
                        }
                    });
                }
            }
            if (rightChess != null) {
                boolean priorityCanCarry = rightChess.side == this.side || !rightChess.isJointState(room) || this.inspireState.isOpen;//优先级是否合格：背对方时，对方棋子如果处于联手状态，猪必须是激励神猪才能背
                boolean beOtherBit = rightChess.side == this.side && rightChess.bittenState.isOpen; //该棋子是己方棋子且被对方狗咬住了,不能咬
                if (priorityCanCarry && !beOtherBit) {
                    room.setCustomButtonClickListener(side, "携带[" + rightChess.name + "]", () -> {   //下发带这个动态按钮
                        carryState.carry(rightChess); //携带右侧棋子
                        cancelCarryButton(room); //取消分发的携带按钮
                        room.setGameTip(room.getCurText() + "移动棋子[猪-" + carryState.carryingChess.name + "]");
                        room.noticeAllRefreshGameInfo();
                        if (isCanExchange(room)) {
                            putBeforeMoveExchangeButton(room); //分发移动之前的可交换按钮
                        }
                    });
                }
            }
        }

        //取消分发的携带按钮
        private void cancelCarryButton(ZodiacChessRoom room) {
            Chess leftChess = room.getChessByPos(this.row, this.col - 1); //获取左侧一个棋子
            if (leftChess != null) room.removeCustomButtonClickListener(side, "携带[" + leftChess.name + "]");
            Chess rightChess = room.getChessByPos(this.row, this.col + 1); //获取右侧一个棋子
            if (rightChess != null) room.removeCustomButtonClickListener(side, "携带[" + rightChess.name + "]");
        }

        @Override
        public void onChosen(ZodiacChessRoom room) {
            putCarryButton(room);
            super.onChosen(room);
        }

        @Override
        public void onCancelChosen(ZodiacChessRoom room) {
            cancelCarryButton(room);
            cancelExchangeButtons(room);
            carryState.down();
        }

        @Override
        public boolean isPosCanMove(ZodiacChessRoom room, int row, int col) {
            if (carryState.carryingChess != null) { //有携带棋子
                if (Math.abs(row - this.row) > 0) return false; //只能横移
                int leftCol = Math.min(this.col, carryState.carryingChess.col); //两者联合的最左侧纵坐标
                int rightCol = Math.max(this.col, carryState.carryingChess.col); //两者联合的最右侧纵坐标
                if (col < leftCol && !room.isPosStraightMeet(row, col, this.row, leftCol)) return false;
                if (col > rightCol && !room.isPosStraightMeet(row, col, this.row, rightCol)) return false;
                return room.curGrids[row][col] == 0; //只能移动到空格
            }
            else { //没有携带，猪自己走
                if (Math.abs(row - this.row) > 1) return false; //纵向移动只能走1格
                if (!room.isPosStraightMeet(row, col, this.row, this.col)) return false; //横移可以无限格但是不能越子
                return room.generalMoveLimit(this, row, col);
            }
        }

        @Override
        public void onMoved(ZodiacChessRoom room, int newRow, int newCol) {
            cancelCarryButton(room); //移动了就要取消携带按钮
            if (carryState.carryingChess != null) {
                int leftCol = Math.min(this.col, carryState.carryingChess.col); //两者联合的最左侧纵坐标
                int rightCol = Math.max(this.col, carryState.carryingChess.col); //两者联合的最右侧纵坐标
                int colDif = 0;
                if (newCol < leftCol) {
                    colDif = newCol - leftCol;
                }
                else if (newCol > rightCol) {
                    colDif = newCol - rightCol;
                }
                carryState.carryingChess.col = carryState.carryingChess.col + colDif; //被携带的棋子执行携带移动（不触发特效检查喝提子检查）
                this.col = this.col + colDif; //自身移动（不触发特效检查喝提子检查）
                recentMoveStep = room.step; //移动了就要记录以及移动
                if (isCanExchange(room)) { //还要交换按钮要分发
                    putAfterMoveExchangeButton(room); //分发移动之后的交换按钮，等交换按钮处理过在检查特效
                }
                else { //没有交换按钮了直接结算
                    room.doPutChessAction(carryState.carryingChess, carryState.carryingChess.row, carryState.carryingChess.col); //前面移动过了，只检查特效
                    super.onMoved(room, this.row, this.col); //前面移动过了，只检查特效
                    cancelExchangeButtons(room);
                    carryState.down();
                }
            }
            else {
                super.onMoved(room, newRow, newCol); //执行自己的移动
            }
        }

        private void putBeforeMoveExchangeButton(ZodiacChessRoom room) {
            room.setCustomButtonClickListener(side, "交换", () -> {
                int tempCol = this.col;
                this.col = carryState.carryingChess.col;
                this.recentMoveStep = room.step; //一旦交换了就标记为移动过
                carryState.carryingChess.col = tempCol;
                carryState.isExchange = true; //一旦交换过，移动之后就不能再交换了
                room.addPutArrow(this.row, this.col, this.row, carryState.carryingChess.col);
                room.addMoveArrow(this.row, carryState.carryingChess.col, this.row, this.col);
                //一旦交换了携带的棋子，那么移动就必须携带横移
                room.playerClickGridEventListener = (room1, player, row, col, side) -> {
                    if (isPosCanMoveFace(room1, row, col)) { //点击了可移动到的位置时
                        room.waitChooseChesses = null; //一旦有棋子移动了，就不要再指定待选了
                        room.playerClickGridEventListener = null; //一旦移动了就制空点击事件
                        room1.doMoveChessAction(this, row, col); //执行移动操作
                    }
                };
                //也可以选择不移动直接放下
                room.setCustomButtonClickListener(side, "放下", () -> {
                    //模拟点击移动后的流程
                    room.waitChooseChesses = null;
                    room.playerClickGridEventListener = null;
                    carryState.carryingChess.onPut(room, carryState.carryingChess.row, carryState.carryingChess.col);
                    this.onMoved(room, this.row, this.col);
                    room.choose = null;
                    room.roundInteractiveActionsChain.checkRoundOver();
                });
                room.noticeAllRefreshGameInfo();
            });
        }

        private void putAfterMoveExchangeButton(ZodiacChessRoom room) {
            room.interruptEndCheckFlag = true;
            room.setCustomButtonClickListener(side, "交换", () -> {
                room.removeCustomButtonClickListener(side, "不换");

                int tempCol = this.col;
                this.col = carryState.carryingChess.col;
                carryState.carryingChess.col = tempCol;
                room.addPutArrow(this.row, this.col, this.row, carryState.carryingChess.col);
                room.addMoveArrow(this.row, carryState.carryingChess.col, this.row, this.col);

                room.interruptEndCheckFlag = false;
                carryState.carryingChess.onPut(room, carryState.carryingChess.row, carryState.carryingChess.col);
                super.onMoved(room, this.row, this.col);
                carryState.down();

                room.roundInteractiveActionsChain.checkRoundOver(); //执行主动移动行为结束判断
            });
            room.setCustomButtonClickListener(side, "不换", () -> {
                room.removeCustomButtonClickListener(side, "交换");

                room.interruptEndCheckFlag = false;
                carryState.carryingChess.onPut(room, carryState.carryingChess.row, carryState.carryingChess.col);
                super.onMoved(room, this.row, this.col);
                carryState.down();

                room.roundInteractiveActionsChain.checkRoundOver(); //执行主动移动行为结束判断
            });
        }

        private void cancelExchangeButtons(ZodiacChessRoom room) {
            room.removeCustomButtonClickListener(side, "交换");
            room.removeCustomButtonClickListener(side, "不换");
            room.removeCustomButtonClickListener(side, "放下");
        }
    }

    /**
     * 玩家点击棋盘格子事件监听器
     */
    private interface IPlayerClickGridEventListener {
        void onPlayerClickGrid(ZodiacChessRoom room, Player player, int row, int col, int side);
    }

    /**
     * 一回合内玩家的交互行动链
     */
    private static class RoundInteractiveActionsChain {

        private final ZodiacChessRoom room;
        private final Player player;
        private String actionGameTip;
        private IPlayerClickGridEventListener playerClickGridEventListener;
        private List<CustomBottom> customBottomList;
        private RoundInteractiveActionsChain nextNode;

        public RoundInteractiveActionsChain(ZodiacChessRoom zodiacChessRoom, Player player) {
            this.room = zodiacChessRoom;
            this.player = player;
        }

        /**
         * 分发当前的交互设置
         */
        public void dispatchInteractiveSetting() {
            if (this.actionGameTip != null) {
                room.gameTip = room.getCurText() + this.actionGameTip;
            }
            room.playerClickGridEventListener = this.playerClickGridEventListener;
            if (customBottomList != null) {
                player.registerCustomBottom(customBottomList.toArray(new CustomBottom[0]));
            }
            room.noticeAllRefreshGameInfo();
        }


        /**
         * 检查是否已经结束
         */
        public void checkRoundOver() {
            if (room.canSeduceChesses == null) { //兔没有和像鸡那样用连锁的方式所以要特殊判断，否则兔移动后既能吸又能提子时，吸会被提子覆盖掉
                if (nextNode != null) {
                    room.roundInteractiveActionsChain = nextNode;
                    nextNode.dispatchInteractiveSetting();
                }
                else {
                    room.onDoActionOver();
                }
            }
        }

        public String getActionGameTip() {
            return actionGameTip;
        }


        public void setActionGameTip(String actionGameTip) {
            this.actionGameTip = actionGameTip;
        }

        public IPlayerClickGridEventListener getPlayerClickGridEventListener() {
            return playerClickGridEventListener;
        }

        public void setPlayerClickGridEventListener(IPlayerClickGridEventListener playerClickGridEventListener) {
            this.playerClickGridEventListener = playerClickGridEventListener;
        }

        public List<CustomBottom> getCustomBottomList() {
            return customBottomList;
        }

        public void setCustomBottomList(List<CustomBottom> customBottomList) {
            this.customBottomList = customBottomList;
        }

        public RoundInteractiveActionsChain getNextNode() {
            return nextNode;
        }

        public void setNextNode(RoundInteractiveActionsChain nextNode) {
            this.nextNode = nextNode;
        }
    }
}
